validateLevelCurve function

SdkFailure<void>? validateLevelCurve(
  1. List<LevelDefinition> levels
)

Checks a level curve's structural invariants before it's ever used to grant XP — a broken curve is rejected loudly at construction time (PlayerProgressionService's constructor throws) rather than producing silently-wrong level math later.

Implementation

SdkFailure<void>? validateLevelCurve(List<LevelDefinition> levels) {
  if (levels.isEmpty) {
    return const SdkFailure(
      kind: SdkErrorKind.validation,
      message: 'Level curve must not be empty',
    );
  }
  final sorted = [...levels]..sort((a, b) => a.level.compareTo(b.level));
  for (var i = 0; i < sorted.length; i++) {
    final expected = i + 1;
    if (sorted[i].level != expected) {
      return SdkFailure(
        kind: SdkErrorKind.validation,
        message:
            'Level curve must be contiguous starting at 1 — expected level '
            '$expected, found ${sorted[i].level} (duplicate or missing level)',
      );
    }
  }

  var cumulative = 0;
  for (var i = 0; i < sorted.length; i++) {
    final def = sorted[i];
    final isLast = i == sorted.length - 1;
    if (isLast) {
      if (def.xpToNext != 0) {
        return const SdkFailure(
          kind: SdkErrorKind.validation,
          message:
              'Last level in the curve must have xpToNext = 0 (marks max level)',
        );
      }
      continue;
    }
    if (def.xpToNext <= 0) {
      return SdkFailure(
        kind: SdkErrorKind.validation,
        message: 'Level ${def.level} must have xpToNext > 0',
      );
    }
    if (i > 0 && def.xpToNext < sorted[i - 1].xpToNext) {
      return SdkFailure(
        kind: SdkErrorKind.validation,
        message:
            'Level curve must be non-decreasing (level ${def.level} costs less than the previous level)',
      );
    }
    cumulative += def.xpToNext;
    if (cumulative < 0 || cumulative > _maxSupportedCumulativeXp) {
      return SdkFailure(
        kind: SdkErrorKind.validation,
        message: 'Level curve cumulative XP overflows the supported range',
      );
    }
  }
  return null;
}