part of 'command_executor.dart'; extension _CommandExecutorValidation on CommandExecutor { void _validate(RuntimeCommand command) { if (!RuntimeCommandType.isSupported(command.type)) { throw UnsupportedError('Unsupported runtime command: ${command.type}'); } RuntimeProtocolSchema.ensureKnownKeys( command.payload, allowed: RuntimeProtocolSchema.allowedCommandPayloadFields(command.type), context: 'RuntimeCommand.${command.type}.payload', ); _optionalString(command.payload['id'], 'id'); _optionalString(command.payload['group'], 'group'); _optionalString(command.payload['commandGroup'], 'commandGroup'); _optionalString(command.payload['scope'], 'scope'); _estimatedDuration(command); } double _estimatedDuration(RuntimeCommand command) { _optionalString(command.payload['onComplete'], 'onComplete'); switch (command.type) { case RuntimeCommandType.movePath: _requiredTarget(command); _validatePath(command.payload['path']); return _duration(command, defaultValue: 0.4); case RuntimeCommandType.moveTo: _requiredTarget(command); _requiredVector(command); return _duration(command, defaultValue: 0.2); case RuntimeCommandType.fadeTo: _requiredTarget(command); _requiredNormalizedDouble(command.payload['alpha'], 'fade_to.alpha'); return _duration(command, defaultValue: 0.2); case RuntimeCommandType.scaleTo: _requiredTarget(command); _requiredDouble(command.payload['scale'], 'scale_to.scale'); return _duration(command, defaultValue: 0.2); case RuntimeCommandType.rotateTo: _requiredTarget(command); _requiredDouble(command.payload['angle'], 'rotate_to.angle'); return _duration(command, defaultValue: 0.2); case RuntimeCommandType.removeNode: _requiredTarget(command); return 0; case RuntimeCommandType.delay: return _duration(command, defaultValue: 0); case RuntimeCommandType.sequence: return _commandsFromPayload( command, ).fold(0, (sum, child) => sum + _estimatedDuration(child)); case RuntimeCommandType.parallel: var maxDuration = 0.0; for (final child in _commandsFromPayload(command)) { final duration = _estimatedDuration(child); if (duration > maxDuration) { maxDuration = duration; } } return maxDuration; case RuntimeCommandType.toast: _toastText(command); return _duration(command, defaultValue: 1.8); case RuntimeCommandType.playSound: _requiredAudioResource(command); _optionalVolume(command); return 0; case RuntimeCommandType.playBgm: _requiredAudioResource(command); _optionalVolume(command); _audioChannel(command); _optionalBool(command.payload['loop'], 'play_bgm.loop'); return 0; case RuntimeCommandType.pauseBgm: case RuntimeCommandType.resumeBgm: case RuntimeCommandType.stopBgm: _audioChannel(command); return 0; case RuntimeCommandType.preloadResources: _requiredResourceGroup(command); _optionalBool( command.payload['failOnError'], 'preload_resources.failOnError', ); return 0; case RuntimeCommandType.evictResources: _requiredResourceGroup(command); return 0; case RuntimeCommandType.cancelCommands: _validateCancelCommands(command); return 0; case RuntimeCommandType.playSpineAnimation: _requiredTarget(command); _requiredSpineAnimation(command); final track = _optionalInt( command.payload['track'], 'play_spine_animation.track', ); if (track != null && track < 0) { throw const FormatException( 'play_spine_animation.track must be >= 0', ); } _optionalBool(command.payload['loop'], 'play_spine_animation.loop'); _optionalBool(command.payload['queue'], 'play_spine_animation.queue'); final delay = _readDouble(command.payload['delay']); if (delay != null && delay < 0) { throw const FormatException( 'play_spine_animation.delay must be >= 0', ); } return 0; case RuntimeCommandType.copyText: _requiredText(command, 'copy_text.text'); return 0; default: throw UnsupportedError('Unsupported runtime command: ${command.type}'); } } void _validatePath(Object? pathValue) { if (pathValue is! List || pathValue.isEmpty) { throw const FormatException('move_path.path must be a non-empty list'); } for (final point in pathValue) { if (point is! Map) { throw const FormatException('move_path.path item must be a map'); } final x = _readDouble(point['x']); final y = _readDouble(point['y']); if (x == null || y == null) { throw const FormatException('move_path point requires x/y'); } } } List _commandsFromPayload(RuntimeCommand command) { final value = command.payload['commands']; if (value is! List) { throw FormatException('${command.type}.commands must be a list'); } return value .map((item) { if (item is! Map) { throw FormatException( '${command.type}.commands item must be a map', ); } return RuntimeCommand.fromMap(Map.from(item)); }) .toList(growable: false); } }