@plasius/learning 0.2.21 → 0.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,22 +7,41 @@ The package does **not** provide HTTP handlers, persistence, authentication, Tok
7
7
  ## Junior Coder catalog
8
8
 
9
9
  The initial immutable catalog is exported as
10
- `JUNIOR_CODER_ROBOT_RESCUE_PATH_V1`. The current uniformly priced pilot
11
- catalog is exported as `JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1`, with
12
- `JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT` available to adapters that
13
- intentionally follow published successors. Each contains 19 independently
14
- sellable, self-contained project modules:
10
+ `JUNIOR_CODER_ROBOT_RESCUE_PATH_V1`, and its uniformly priced successor remains
11
+ available as `JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1`. Path `1.2.0` upgrades Road
12
+ Hopper Rally, path `1.3.0` adds Paddle Pulse `2.0.0`, and path `1.4.0` is
13
+ exported as `JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_4` and selected by
14
+ `JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT`. It advances Road Hopper Rally to the
15
+ evidence-led module `2.1.0`; the other eighteen module records retain their
16
+ prior immutable versions. Each path
17
+ contains 19 independently sellable, self-contained project modules:
15
18
 
16
19
  - 8 original arcade game modules;
17
20
  - 5 simulator-backed robotics modules;
18
21
  - 3 constrained Vibe Coding modules;
19
22
  - 3 web application modules.
20
23
 
21
- Both catalogs remain in `pilot-grant-only` commercial state. The immutable
22
- `1.1.0` modules each cost 50 Tokens (50,000 subunits), carrying a
24
+ All catalogs remain in `pilot-grant-only` commercial state. The immutable
25
+ `1.1.0` modules, Road Hopper Rally `2.1.0` and Paddle Pulse `2.0.0` each cost 50 Tokens (50,000 subunits), carrying a
23
26
  non-redeemable £5 reference value. Price metadata is not authorization to
24
27
  enable public checkout.
25
28
 
29
+ Road Hopper Rally `2.1.0` is a 450-minute, six-mission catalog record whose
30
+ 54 evidence-led activities, starter project, deterministic runtime, original
31
+ assets and assessment definitions live in
32
+ `@plasius/learning-road-hopper-rally@1.1.0`. The catalog pins the exact
33
+ `ROAD_HOPPER_RALLY_COURSE_V3` export, schema `3` and its canonical SHA-256 digest
34
+ through `ExternalLearningContentReferenceV1`; it does not import or bundle the
35
+ executable content package. Road Hopper `2.0.0` and its package `1.0.0`
36
+ reference remain available unchanged for rollback and existing consumers.
37
+
38
+ Paddle Pulse `2.0.0` is a 360-minute, six-mission catalog record whose 54-stage
39
+ course, declarative program contracts, deterministic 60 Hz engine and protected
40
+ assessment live in `@plasius/learning-paddle-pulse@0.1.0`. The catalog pins the
41
+ exact `PADDLE_PULSE_MODULE_V2` export, schema `2` and canonical SHA-256 digest
42
+ without importing the package. Paddle Pulse `1.1.0` and its mission-authoring
43
+ export remain available unchanged.
44
+
26
45
  ## Install
27
46
 
28
47
  ```bash
@@ -40,6 +59,20 @@ import {
40
59
  assertValidLearningPath(JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT);
41
60
  ```
42
61
 
62
+ Consumers resolving external course content must verify all five reference
63
+ fields before use:
64
+
65
+ ```ts
66
+ import {
67
+ ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V2,
68
+ isExternalLearningContentReferenceV1,
69
+ } from "@plasius/learning";
70
+
71
+ if (!isExternalLearningContentReferenceV1(ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V2)) {
72
+ throw new Error("Invalid external learning content reference");
73
+ }
74
+ ```
75
+
43
76
  ## Calculate an assessment
44
77
 
45
78
  ```ts
@@ -303,6 +336,8 @@ assertValidMissionAuthoringBundle(
303
336
  ## Contract rules
304
337
 
305
338
  - Published IDs and versions are immutable.
339
+ - External course content uses exact package and export names, a stable semantic
340
+ version and a lower-case canonical SHA-256 digest; version ranges are invalid.
306
341
  - Reference prices are product-copy metadata and never create cash redemption
307
342
  rights.
308
343
  - A module entitlement must bind to an exact module version.
package/dist/index.cjs CHANGED
@@ -28,18 +28,28 @@ __export(index_exports, {
28
28
  CONTEXTUAL_VOICE_SUGGESTED_ACTIONS_V1: () => CONTEXTUAL_VOICE_SUGGESTED_ACTIONS_V1,
29
29
  CREATURE_CARE_DASHBOARD_MISSION_ONE_AUTHORING_V1: () => CREATURE_CARE_DASHBOARD_MISSION_ONE_AUTHORING_V1,
30
30
  DANCE_ROVER_MISSION_ONE_AUTHORING_V1: () => DANCE_ROVER_MISSION_ONE_AUTHORING_V1,
31
+ EXTERNAL_LEARNING_CONTENT_REFERENCE_VERSION_V1: () => EXTERNAL_LEARNING_CONTENT_REFERENCE_VERSION_V1,
31
32
  JUNIOR_CODER_MISSION_STAGE_ORDER_V1: () => JUNIOR_CODER_MISSION_STAGE_ORDER_V1,
32
33
  JUNIOR_CODER_MODULE_PRICE_V1_1: () => JUNIOR_CODER_MODULE_PRICE_V1_1,
34
+ JUNIOR_CODER_PADDLE_PULSE_V2: () => JUNIOR_CODER_PADDLE_PULSE_V2,
35
+ JUNIOR_CODER_ROAD_HOPPER_RALLY_V2: () => JUNIOR_CODER_ROAD_HOPPER_RALLY_V2,
36
+ JUNIOR_CODER_ROAD_HOPPER_RALLY_V2_1: () => JUNIOR_CODER_ROAD_HOPPER_RALLY_V2_1,
33
37
  JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT: () => JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT,
34
38
  JUNIOR_CODER_ROBOT_RESCUE_PATH_V1: () => JUNIOR_CODER_ROBOT_RESCUE_PATH_V1,
35
39
  JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1: () => JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1,
40
+ JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_2: () => JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_2,
41
+ JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_3: () => JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_3,
42
+ JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_4: () => JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_4,
36
43
  METEOR_SHIELD_MISSION_ONE_AUTHORING_V1: () => METEOR_SHIELD_MISSION_ONE_AUTHORING_V1,
37
44
  MISSION_AUTHORING_CONTRACT_VERSION_V1: () => MISSION_AUTHORING_CONTRACT_VERSION_V1,
38
45
  OBSTACLE_EXPLORER_MISSION_ONE_AUTHORING_V1: () => OBSTACLE_EXPLORER_MISSION_ONE_AUTHORING_V1,
46
+ PADDLE_PULSE_EXTERNAL_CONTENT_V1: () => PADDLE_PULSE_EXTERNAL_CONTENT_V1,
39
47
  PADDLE_PULSE_MISSION_ONE_AUTHORING_V1: () => PADDLE_PULSE_MISSION_ONE_AUTHORING_V1,
40
48
  PIXEL_TRAIL_CHALLENGE_MISSION_ONE_AUTHORING_V1: () => PIXEL_TRAIL_CHALLENGE_MISSION_ONE_AUTHORING_V1,
41
49
  RAINBOW_RESCUE_ROVER_MISSION_ONE_AUTHORING_V1: () => RAINBOW_RESCUE_ROVER_MISSION_ONE_AUTHORING_V1,
42
50
  RESCUE_CREW_COMMANDER_MISSION_ONE_AUTHORING_V1: () => RESCUE_CREW_COMMANDER_MISSION_ONE_AUTHORING_V1,
51
+ ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V1: () => ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V1,
52
+ ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V2: () => ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V2,
43
53
  ROAD_HOPPER_RALLY_MISSION_ONE_AUTHORING_V1: () => ROAD_HOPPER_RALLY_MISSION_ONE_AUTHORING_V1,
44
54
  ROBOT_MAZE_DASH_MISSION_ONE_AUTHORING_V1: () => ROBOT_MAZE_DASH_MISSION_ONE_AUTHORING_V1,
45
55
  ROBOT_MISSION_CONTROL_MISSION_ONE_AUTHORING_V1: () => ROBOT_MISSION_CONTROL_MISSION_ONE_AUTHORING_V1,
@@ -67,6 +77,7 @@ __export(index_exports, {
67
77
  assertValidStaticProjectSnapshot: () => assertValidStaticProjectSnapshot,
68
78
  assertValidVoiceHelpAvailability: () => assertValidVoiceHelpAvailability,
69
79
  calculateAssessment: () => calculateAssessment,
80
+ isExternalLearningContentReferenceV1: () => isExternalLearningContentReferenceV1,
70
81
  validateAssessmentRubric: () => validateAssessmentRubric,
71
82
  validateLearningPath: () => validateLearningPath,
72
83
  validateMissionAuthoringBundle: () => validateMissionAuthoringBundle,
@@ -644,9 +655,285 @@ var JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1 = {
644
655
  }
645
656
  }))
646
657
  };
647
- var JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT = JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1;
658
+ var ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V1 = Object.freeze({
659
+ packageName: "@plasius/learning-road-hopper-rally",
660
+ packageVersion: "1.0.0",
661
+ exportName: "ROAD_HOPPER_RALLY_COURSE_V2",
662
+ schemaVersion: "2",
663
+ sha256: "1a20741beba028004e0be527d05aae2b2881082d3b578e0d62308a59bf1323f0"
664
+ });
665
+ var ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V2 = Object.freeze({
666
+ packageName: "@plasius/learning-road-hopper-rally",
667
+ packageVersion: "1.1.0",
668
+ exportName: "ROAD_HOPPER_RALLY_COURSE_V3",
669
+ schemaVersion: "3",
670
+ sha256: "f73b90f5f0e483b96724f978f2977435a5e6a1333f5f45fd0fc803b55ad21187"
671
+ });
672
+ var roadHopperV1_1 = JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1.modules.find(
673
+ (entry) => entry.id === "junior-coder.road-hopper-rally"
674
+ );
675
+ var ROAD_HOPPER_RALLY_V2_MISSIONS = [
676
+ {
677
+ id: "board",
678
+ title: "Build the Rally Board",
679
+ concepts: ["coordinate grids", "arrays", "entity state", "rendering"],
680
+ statement: "Create the road, median, river and five distinct home bays in board.js.",
681
+ sideAdventure: "Design an original high-contrast board palette without changing its mechanics."
682
+ },
683
+ {
684
+ id: "hopper",
685
+ title: "Move the Hopper",
686
+ concepts: ["events", "bounded movement", "keyboard input", "touch input", "respawning"],
687
+ statement: "Implement four-direction tile movement, bounds, equivalent inputs and respawning in hopper.js.",
688
+ sideAdventure: "Add an accessible movement status phrase for every successful hop."
689
+ },
690
+ {
691
+ id: "traffic",
692
+ title: "Create Rally Traffic",
693
+ concepts: ["spawning", "fixed-step updates", "wrapping", "collision detection", "difficulty"],
694
+ statement: "Implement five deterministic traffic lanes, collisions, lives and difficulty in traffic.js.",
695
+ sideAdventure: "Invent an original vehicle type while preserving the entity and collision limits."
696
+ },
697
+ {
698
+ id: "river",
699
+ title: "Cross the Moving River",
700
+ concepts: ["moving platforms", "carrying", "hazards", "state machines", "off-screen bounds"],
701
+ statement: "Implement logs, carrying, water deaths, diving platforms and edge hazards in river.js.",
702
+ sideAdventure: "Create an original visual warning for a platform that is about to dive."
703
+ },
704
+ {
705
+ id: "rules",
706
+ title: "Add Rally Rules",
707
+ concepts: ["timers", "lives", "scoring", "bonuses", "levels", "game over"],
708
+ statement: "Implement homes, occupied and blocked bays, timer, scoring, bonuses and level progression in rules.js.",
709
+ sideAdventure: "Tune a practice-speed preset that keeps every rule deterministic."
710
+ },
711
+ {
712
+ id: "game",
713
+ title: "Complete Road Hopper Rally",
714
+ concepts: ["system assembly", "pause and restart", "alternating players", "audio", "accessibility", "testing"],
715
+ statement: "Assemble the systems, accessibility modes and alternating-player finale in game.js, then pass the protected challenge.",
716
+ sideAdventure: "Polish the finished game with original captions, visual cues and reduced-motion feedback."
717
+ }
718
+ ];
719
+ var JUNIOR_CODER_ROAD_HOPPER_RALLY_V2 = {
720
+ ...roadHopperV1_1,
721
+ version: "2.0.0",
722
+ contentRevision: "2026-08-11.1",
723
+ summary: "Recreate the systems behind a classic road-and-river crossing game using original Road Hopper Rally code, art, audio and presentation.",
724
+ estimatedMinutes: 450,
725
+ tools: [
726
+ "JavaScript",
727
+ "QuickJS sandbox",
728
+ "Deterministic 30 Hz engine",
729
+ "Road Hopper renderer"
730
+ ],
731
+ concepts: [
732
+ "coordinates",
733
+ "arrays and entity state",
734
+ "events and input",
735
+ "fixed-step update and render",
736
+ "spawning and movement",
737
+ "collision detection",
738
+ "state machines",
739
+ "timers, lives and scoring",
740
+ "difficulty progression",
741
+ "audio integration",
742
+ "debugging",
743
+ "deterministic testing"
744
+ ],
745
+ missions: ROAD_HOPPER_RALLY_V2_MISSIONS.map((missionDefinition, index) => ({
746
+ id: `road-hopper-rally-mission-${index + 1}-${missionDefinition.id}`,
747
+ title: missionDefinition.title,
748
+ estimatedMinutes: 75,
749
+ concepts: [...missionDefinition.concepts],
750
+ goals: [
751
+ {
752
+ id: `road-hopper-rally-v2-${missionDefinition.id}`,
753
+ statement: missionDefinition.statement,
754
+ evidence: "assessment"
755
+ }
756
+ ],
757
+ sideAdventure: missionDefinition.sideAdventure
758
+ })),
759
+ assessment: {
760
+ version: "2.0.0",
761
+ completionScore: 80,
762
+ criteria: [
763
+ { id: "road-hopper-v2-structure", label: "All six bounded JavaScript files compile and assemble.", dimension: "structure", points: 20, mandatory: true, visibility: "visible" },
764
+ { id: "road-hopper-v2-road", label: "Board, input and traffic behaviours pass deterministic scenarios.", dimension: "behaviour", points: 15, mandatory: true, visibility: "visible" },
765
+ { id: "road-hopper-v2-river", label: "River platforms, carrying and hazards pass deterministic scenarios.", dimension: "behaviour", points: 15, mandatory: true, visibility: "visible" },
766
+ { id: "road-hopper-v2-rules", label: "Homes, timer, lives, scoring, bonuses and levels behave correctly.", dimension: "behaviour", points: 20, mandatory: true, visibility: "visible" },
767
+ { id: "road-hopper-v2-final", label: "The complete game passes the server-only protected final challenge.", dimension: "resilience", points: 20, mandatory: true, visibility: "protected" },
768
+ { id: "road-hopper-v2-safety", label: "The project stays within the sandbox, resource and privacy boundaries.", dimension: "safety", points: 10, mandatory: true, visibility: "visible" }
769
+ ]
770
+ },
771
+ badges: [
772
+ {
773
+ id: "road-hopper-rally-v2-complete",
774
+ title: "Road Hopper Rally Champion",
775
+ evidence: "module-score",
776
+ tradeable: false,
777
+ tokenConvertible: false
778
+ }
779
+ ],
780
+ externalContent: ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V1
781
+ };
782
+ var ROAD_HOPPER_RALLY_V2_1_MISSION_TITLES = [
783
+ "Map the Rally Route",
784
+ "Move the Hopper",
785
+ "Build the Traffic Rally",
786
+ "Ride the Rescue River",
787
+ "Score the Rescue",
788
+ "Road Hopper Rally Challenge"
789
+ ];
790
+ var JUNIOR_CODER_ROAD_HOPPER_RALLY_V2_1 = {
791
+ ...JUNIOR_CODER_ROAD_HOPPER_RALLY_V2,
792
+ version: "2.1.0",
793
+ contentRevision: "2026-08-24.1",
794
+ summary: "Build Road Hopper Rally through 54 evidence-led activities: predict, edit, run, inspect, repair, explain and prove each deterministic game system.",
795
+ missions: JUNIOR_CODER_ROAD_HOPPER_RALLY_V2.missions.map((mission2, index) => ({
796
+ ...mission2,
797
+ title: ROAD_HOPPER_RALLY_V2_1_MISSION_TITLES[index]
798
+ })),
799
+ assessment: {
800
+ ...JUNIOR_CODER_ROAD_HOPPER_RALLY_V2.assessment,
801
+ version: "2.1.0"
802
+ },
803
+ externalContent: ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V2
804
+ };
805
+ var JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_2 = {
806
+ ...JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1,
807
+ version: "1.2.0",
808
+ modules: JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1.modules.map((entry) => entry.id === JUNIOR_CODER_ROAD_HOPPER_RALLY_V2.id ? JUNIOR_CODER_ROAD_HOPPER_RALLY_V2 : entry)
809
+ };
810
+ var PADDLE_PULSE_EXTERNAL_CONTENT_V1 = Object.freeze({
811
+ packageName: "@plasius/learning-paddle-pulse",
812
+ packageVersion: "0.1.0",
813
+ exportName: "PADDLE_PULSE_MODULE_V2",
814
+ schemaVersion: "2",
815
+ sha256: "19f3666bdb523a3faf069341b0bc748996b3db282f89171e3c9f0a373a8c8f7e"
816
+ });
817
+ var paddlePulseV1_1 = JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1.modules.find(
818
+ (entry) => entry.id === "junior-coder.paddle-pulse"
819
+ );
820
+ var PADDLE_PULSE_V2_MISSIONS = [
821
+ {
822
+ id: "court-coordinates",
823
+ title: "Court & Coordinates",
824
+ concepts: ["coordinates", "canvas primitives", "game loops", "bounds"],
825
+ statement: "Build a responsive 640 \xD7 480 logical court with a bounded paddle and ball.",
826
+ sideAdventure: "Create an original high-contrast court palette without changing its geometry."
827
+ },
828
+ {
829
+ id: "paddle-controls",
830
+ title: "Paddle Controls",
831
+ concepts: ["input state", "Pointer Events", "keyboard input", "clamping"],
832
+ statement: "Implement equivalent Arrow/A/D and direct horizontal pointer-drag controls.",
833
+ sideAdventure: "Add a semantic movement trace that does not rely on animation."
834
+ },
835
+ {
836
+ id: "ball-motion",
837
+ title: "Ball Motion & Bounces",
838
+ concepts: ["velocity", "fixed timesteps", "reflection", "contact normals", "paddle angles"],
839
+ statement: "Implement deterministic ball motion plus controlled wall and paddle reflections.",
840
+ sideAdventure: "Design a reduced-motion trajectory inspector for one rally."
841
+ },
842
+ {
843
+ id: "bricks-collisions",
844
+ title: "Bricks & Collisions",
845
+ concepts: ["arrays", "loops", "brick grids", "AABB collision", "resolution"],
846
+ statement: "Create an original brick grid whose bricks score once and disappear on impact.",
847
+ sideAdventure: "Invent a new visual brick pattern using the same bounded collision rules."
848
+ },
849
+ {
850
+ id: "score-lives-states",
851
+ title: "Score, Lives & States",
852
+ concepts: ["state machines", "invariants", "scoring", "lives", "HUD state"],
853
+ statement: "Implement serve, pause, life loss, level clear, game over, score and HUD states.",
854
+ sideAdventure: "Add a printable state-transition table for a facilitator or screen-reader user."
855
+ },
856
+ {
857
+ id: "levels-final-game",
858
+ title: "Levels, Sound & Final Game",
859
+ concepts: ["level data", "difficulty", "responsive rendering", "accessible audio", "testing"],
860
+ statement: "Assemble multiple original levels, optional generated cues and the integrated final game.",
861
+ sideAdventure: "After completion, add an optional original power-up without changing core assessment."
862
+ }
863
+ ];
864
+ var JUNIOR_CODER_PADDLE_PULSE_V2 = {
865
+ ...paddlePulseV1_1,
866
+ version: "2.0.0",
867
+ contentRevision: "2026-08-11.1",
868
+ summary: "Build an original Plasius brick-breaker while learning rendering, equivalent input, deterministic physics, collisions, score, lives, states, levels, accessible sound and testing.",
869
+ estimatedMinutes: 360,
870
+ tools: [
871
+ "JavaScript",
872
+ "QuickJS sandbox",
873
+ "Deterministic 60 Hz engine",
874
+ "Responsive Paddle Pulse renderer"
875
+ ],
876
+ concepts: [
877
+ "coordinates and rendering",
878
+ "game loops and fixed timesteps",
879
+ "keyboard and pointer input",
880
+ "velocity and reflection",
881
+ "collision detection and resolution",
882
+ "arrays and brick grids",
883
+ "scoring, lives and state machines",
884
+ "level data and difficulty",
885
+ "accessible generated audio",
886
+ "debugging and deterministic testing"
887
+ ],
888
+ missions: PADDLE_PULSE_V2_MISSIONS.map((missionDefinition, index) => ({
889
+ id: `paddle-pulse-mission-${index + 1}-${missionDefinition.id}`,
890
+ title: missionDefinition.title,
891
+ estimatedMinutes: 60,
892
+ concepts: [...missionDefinition.concepts],
893
+ goals: [{
894
+ id: `paddle-pulse-v2-${missionDefinition.id}`,
895
+ statement: missionDefinition.statement,
896
+ evidence: "assessment"
897
+ }],
898
+ sideAdventure: missionDefinition.sideAdventure
899
+ })),
900
+ assessment: {
901
+ version: "2.0.0",
902
+ completionScore: 80,
903
+ criteria: [
904
+ { id: "paddle-pulse-v2-court", label: "The logical court, paddle and initial scene are finite, responsive and bounded.", dimension: "structure", points: 20, mandatory: false, visibility: "visible" },
905
+ { id: "paddle-pulse-v2-controls", label: "Keyboard and pointer paths produce equivalent clamped paddle input.", dimension: "behaviour", points: 15, mandatory: false, visibility: "visible" },
906
+ { id: "paddle-pulse-v2-motion", label: "Ball motion and wall/paddle reflections pass deterministic trajectories.", dimension: "behaviour", points: 15, mandatory: false, visibility: "visible" },
907
+ { id: "paddle-pulse-v2-bricks", label: "Brick face, corner and adjacent impacts resolve without double-scoring.", dimension: "behaviour", points: 20, mandatory: false, visibility: "visible" },
908
+ { id: "paddle-pulse-v2-states", label: "Score, lives, serve, pause, level-clear and game-over transitions preserve invariants.", dimension: "resilience", points: 10, mandatory: false, visibility: "visible" },
909
+ { id: "paddle-pulse-v2-final", label: "Multiple original levels and the integrated game pass protected deterministic scenarios.", dimension: "resilience", points: 10, mandatory: false, visibility: "protected" },
910
+ { id: "paddle-pulse-v2-safety", label: "Source, commands, runtime resources and assessment remain inside sandbox limits.", dimension: "safety", points: 10, mandatory: true, visibility: "visible" }
911
+ ]
912
+ },
913
+ badges: [
914
+ { id: "paddle-pulse-v2-court-builder", title: "Court Builder", evidence: "mission-score", tradeable: false, tokenConvertible: false },
915
+ { id: "paddle-pulse-v2-paddle-pilot", title: "Paddle Pilot", evidence: "mission-score", tradeable: false, tokenConvertible: false },
916
+ { id: "paddle-pulse-v2-rally-master", title: "Rally Master", evidence: "mission-score", tradeable: false, tokenConvertible: false },
917
+ { id: "paddle-pulse-v2-brick-breaker", title: "Brick Breaker", evidence: "mission-score", tradeable: false, tokenConvertible: false },
918
+ { id: "paddle-pulse-v2-game-keeper", title: "Game Keeper", evidence: "mission-score", tradeable: false, tokenConvertible: false },
919
+ { id: "paddle-pulse-v2-complete", title: "Paddle Pulse Champion", evidence: "module-score", tradeable: false, tokenConvertible: false }
920
+ ],
921
+ externalContent: PADDLE_PULSE_EXTERNAL_CONTENT_V1
922
+ };
923
+ var JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_3 = {
924
+ ...JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_2,
925
+ version: "1.3.0",
926
+ modules: JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_2.modules.map((entry) => entry.id === JUNIOR_CODER_PADDLE_PULSE_V2.id ? JUNIOR_CODER_PADDLE_PULSE_V2 : entry)
927
+ };
928
+ var JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_4 = {
929
+ ...JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_3,
930
+ version: "1.4.0",
931
+ modules: JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_3.modules.map((entry) => entry.id === JUNIOR_CODER_ROAD_HOPPER_RALLY_V2_1.id ? JUNIOR_CODER_ROAD_HOPPER_RALLY_V2_1 : entry)
932
+ };
933
+ var JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT = JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_4;
648
934
 
649
935
  // src/contracts.ts
936
+ var EXTERNAL_LEARNING_CONTENT_REFERENCE_VERSION_V1 = "1";
650
937
  var MISSION_AUTHORING_CONTRACT_VERSION_V1 = "1.0.0";
651
938
 
652
939
  // src/contextual-help.ts
@@ -6838,6 +7125,16 @@ var ROAD_HOPPER_RALLY_MISSION_ONE_AUTHORING_V1 = {
6838
7125
 
6839
7126
  // src/validation.ts
6840
7127
  var CANONICAL_TOKEN_SUBUNITS = /^(0|[1-9][0-9]*)$/u;
7128
+ var EXACT_PACKAGE_NAME = /^@plasius\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
7129
+ var EXACT_SEMVER = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/u;
7130
+ var EXPORT_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
7131
+ var CONTENT_SCHEMA_VERSION = /^(0|[1-9][0-9]*)$/u;
7132
+ var SHA256 = /^[a-f0-9]{64}$/u;
7133
+ function isExternalLearningContentReferenceV1(value) {
7134
+ if (typeof value !== "object" || value === null) return false;
7135
+ const candidate = value;
7136
+ return Object.keys(value).length === 5 && typeof candidate.packageName === "string" && EXACT_PACKAGE_NAME.test(candidate.packageName) && typeof candidate.packageVersion === "string" && EXACT_SEMVER.test(candidate.packageVersion) && typeof candidate.exportName === "string" && EXPORT_NAME.test(candidate.exportName) && typeof candidate.schemaVersion === "string" && CONTENT_SCHEMA_VERSION.test(candidate.schemaVersion) && typeof candidate.sha256 === "string" && SHA256.test(candidate.sha256);
7137
+ }
6841
7138
  function issue(code, message, path, moduleId) {
6842
7139
  return { code, message, path, ...moduleId ? { moduleId } : {} };
6843
7140
  }
@@ -6952,6 +7249,17 @@ function validateModule(module3, moduleIndex) {
6952
7249
  )
6953
7250
  );
6954
7251
  }
7252
+ const externalContent = module3.externalContent;
7253
+ if (externalContent && !isExternalLearningContentReferenceV1(externalContent)) {
7254
+ issues.push(
7255
+ issue(
7256
+ "invalid-external-content-reference",
7257
+ "External content must use an exact @plasius package, stable semantic version, named export, schema version and lower-case SHA-256 digest.",
7258
+ `${base}.externalContent`,
7259
+ module3.id
7260
+ )
7261
+ );
7262
+ }
6955
7263
  return issues;
6956
7264
  }
6957
7265
  function validateLearningPath(path) {
@@ -7002,18 +7310,28 @@ ${summary}`);
7002
7310
  CONTEXTUAL_VOICE_SUGGESTED_ACTIONS_V1,
7003
7311
  CREATURE_CARE_DASHBOARD_MISSION_ONE_AUTHORING_V1,
7004
7312
  DANCE_ROVER_MISSION_ONE_AUTHORING_V1,
7313
+ EXTERNAL_LEARNING_CONTENT_REFERENCE_VERSION_V1,
7005
7314
  JUNIOR_CODER_MISSION_STAGE_ORDER_V1,
7006
7315
  JUNIOR_CODER_MODULE_PRICE_V1_1,
7316
+ JUNIOR_CODER_PADDLE_PULSE_V2,
7317
+ JUNIOR_CODER_ROAD_HOPPER_RALLY_V2,
7318
+ JUNIOR_CODER_ROAD_HOPPER_RALLY_V2_1,
7007
7319
  JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT,
7008
7320
  JUNIOR_CODER_ROBOT_RESCUE_PATH_V1,
7009
7321
  JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1,
7322
+ JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_2,
7323
+ JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_3,
7324
+ JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_4,
7010
7325
  METEOR_SHIELD_MISSION_ONE_AUTHORING_V1,
7011
7326
  MISSION_AUTHORING_CONTRACT_VERSION_V1,
7012
7327
  OBSTACLE_EXPLORER_MISSION_ONE_AUTHORING_V1,
7328
+ PADDLE_PULSE_EXTERNAL_CONTENT_V1,
7013
7329
  PADDLE_PULSE_MISSION_ONE_AUTHORING_V1,
7014
7330
  PIXEL_TRAIL_CHALLENGE_MISSION_ONE_AUTHORING_V1,
7015
7331
  RAINBOW_RESCUE_ROVER_MISSION_ONE_AUTHORING_V1,
7016
7332
  RESCUE_CREW_COMMANDER_MISSION_ONE_AUTHORING_V1,
7333
+ ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V1,
7334
+ ROAD_HOPPER_RALLY_EXTERNAL_CONTENT_V2,
7017
7335
  ROAD_HOPPER_RALLY_MISSION_ONE_AUTHORING_V1,
7018
7336
  ROBOT_MAZE_DASH_MISSION_ONE_AUTHORING_V1,
7019
7337
  ROBOT_MISSION_CONTROL_MISSION_ONE_AUTHORING_V1,
@@ -7041,6 +7359,7 @@ ${summary}`);
7041
7359
  assertValidStaticProjectSnapshot,
7042
7360
  assertValidVoiceHelpAvailability,
7043
7361
  calculateAssessment,
7362
+ isExternalLearningContentReferenceV1,
7044
7363
  validateAssessmentRubric,
7045
7364
  validateLearningPath,
7046
7365
  validateMissionAuthoringBundle,