@plasius/learning 0.4.0 → 0.6.0

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.
Files changed (66) hide show
  1. package/README.md +28 -0
  2. package/dist/chunk-2UIOBMJZ.js +44 -0
  3. package/dist/chunk-2UIOBMJZ.js.map +1 -0
  4. package/dist/courses/beacon-bot.cjs +362 -0
  5. package/dist/courses/beacon-bot.cjs.map +1 -0
  6. package/dist/courses/beacon-bot.d.cts +6 -0
  7. package/dist/courses/beacon-bot.d.ts +6 -0
  8. package/dist/courses/beacon-bot.js +168 -0
  9. package/dist/courses/beacon-bot.js.map +1 -0
  10. package/dist/courses/dance-rover.cjs +374 -0
  11. package/dist/courses/dance-rover.cjs.map +1 -0
  12. package/dist/courses/dance-rover.d.cts +6 -0
  13. package/dist/courses/dance-rover.d.ts +6 -0
  14. package/dist/courses/dance-rover.js +180 -0
  15. package/dist/courses/dance-rover.js.map +1 -0
  16. package/dist/courses/meteor-shield.cjs +369 -0
  17. package/dist/courses/meteor-shield.cjs.map +1 -0
  18. package/dist/courses/meteor-shield.d.cts +6 -0
  19. package/dist/courses/meteor-shield.d.ts +6 -0
  20. package/dist/courses/meteor-shield.js +175 -0
  21. package/dist/courses/meteor-shield.js.map +1 -0
  22. package/dist/courses/obstacle-explorer.cjs +368 -0
  23. package/dist/courses/obstacle-explorer.cjs.map +1 -0
  24. package/dist/courses/obstacle-explorer.d.cts +6 -0
  25. package/dist/courses/obstacle-explorer.d.ts +6 -0
  26. package/dist/courses/obstacle-explorer.js +174 -0
  27. package/dist/courses/obstacle-explorer.js.map +1 -0
  28. package/dist/courses/pixel-trail-challenge.cjs +366 -0
  29. package/dist/courses/pixel-trail-challenge.cjs.map +1 -0
  30. package/dist/courses/pixel-trail-challenge.d.cts +6 -0
  31. package/dist/courses/pixel-trail-challenge.d.ts +6 -0
  32. package/dist/courses/pixel-trail-challenge.js +172 -0
  33. package/dist/courses/pixel-trail-challenge.js.map +1 -0
  34. package/dist/courses/rainbow-rescue-rover.cjs +377 -0
  35. package/dist/courses/rainbow-rescue-rover.cjs.map +1 -0
  36. package/dist/courses/rainbow-rescue-rover.d.cts +6 -0
  37. package/dist/courses/rainbow-rescue-rover.d.ts +6 -0
  38. package/dist/courses/rainbow-rescue-rover.js +183 -0
  39. package/dist/courses/rainbow-rescue-rover.js.map +1 -0
  40. package/dist/courses/rescue-crew-commander.cjs +369 -0
  41. package/dist/courses/rescue-crew-commander.cjs.map +1 -0
  42. package/dist/courses/rescue-crew-commander.d.cts +6 -0
  43. package/dist/courses/rescue-crew-commander.d.ts +6 -0
  44. package/dist/courses/rescue-crew-commander.js +175 -0
  45. package/dist/courses/rescue-crew-commander.js.map +1 -0
  46. package/dist/courses/robot-maze-dash.js +3 -38
  47. package/dist/courses/robot-maze-dash.js.map +1 -1
  48. package/dist/courses/servo-creature.cjs +369 -0
  49. package/dist/courses/servo-creature.cjs.map +1 -0
  50. package/dist/courses/servo-creature.d.cts +6 -0
  51. package/dist/courses/servo-creature.d.ts +6 -0
  52. package/dist/courses/servo-creature.js +175 -0
  53. package/dist/courses/servo-creature.js.map +1 -0
  54. package/dist/courses/skywing-sprint.cjs +367 -0
  55. package/dist/courses/skywing-sprint.cjs.map +1 -0
  56. package/dist/courses/skywing-sprint.d.cts +6 -0
  57. package/dist/courses/skywing-sprint.d.ts +6 -0
  58. package/dist/courses/skywing-sprint.js +173 -0
  59. package/dist/courses/skywing-sprint.js.map +1 -0
  60. package/dist/courses/star-defender-squadron.cjs +369 -0
  61. package/dist/courses/star-defender-squadron.cjs.map +1 -0
  62. package/dist/courses/star-defender-squadron.d.cts +6 -0
  63. package/dist/courses/star-defender-squadron.d.ts +6 -0
  64. package/dist/courses/star-defender-squadron.js +175 -0
  65. package/dist/courses/star-defender-squadron.js.map +1 -0
  66. package/package.json +51 -1
@@ -0,0 +1,369 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/courses/rescue-crew-commander.ts
21
+ var rescue_crew_commander_exports = {};
22
+ __export(rescue_crew_commander_exports, {
23
+ course: () => course,
24
+ practice: () => practice
25
+ });
26
+ module.exports = __toCommonJS(rescue_crew_commander_exports);
27
+
28
+ // src/mission-authoring.ts
29
+ var JUNIOR_CODER_MISSION_STAGE_ORDER_V1 = [
30
+ "learn",
31
+ "predict",
32
+ "build",
33
+ "run",
34
+ "assess",
35
+ "inspect",
36
+ "fix",
37
+ "explain",
38
+ "reward"
39
+ ];
40
+
41
+ // src/course-contracts.ts
42
+ var LEARNING_COURSE_STAGE_ORDER = JUNIOR_CODER_MISSION_STAGE_ORDER_V1;
43
+ var LEARNING_COURSE_LIMITS = Object.freeze({
44
+ missions: 6,
45
+ stagesPerMission: 9,
46
+ projectFiles: 8,
47
+ sourceCharactersPerFile: 64e3,
48
+ sourceCharactersPerProject: 96e3
49
+ });
50
+ var ID = /^[a-z0-9][a-z0-9.-]{0,159}$/u;
51
+ var FILE_PATH = /^[a-z0-9][a-z0-9_-]{0,63}\.(?:js|py|cpp|html|css|json)$/u;
52
+ var VERSION = /^\d+\.\d+\.\d+$/u;
53
+ var LANGUAGES = ["javascript", "python", "cpp", "html", "css", "blocks", "json"];
54
+ var CATEGORIES = ["game", "robot", "vibe", "web-app"];
55
+ var PLACEHOLDER = /\b(?:TODO|TBD|coming soon|placeholder|lorem ipsum)\b/iu;
56
+ var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
57
+ var text = (value, minimum = 1, maximum = 8e3) => typeof value === "string" && value.trim().length >= minimum && value.length <= maximum;
58
+ var id = (value) => typeof value === "string" && ID.test(value);
59
+ var integer = (value, minimum, maximum) => typeof value === "number" && Number.isSafeInteger(value) && value >= minimum && value <= maximum;
60
+ var exactKeys = (value, keys) => Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
61
+ var LearningCourseInputError = class extends Error {
62
+ constructor() {
63
+ super("Invalid learning course input.");
64
+ this.name = "LearningCourseInputError";
65
+ }
66
+ };
67
+ function fileDefinitions(value) {
68
+ return Array.isArray(value) && value.length >= 1 && value.length <= LEARNING_COURSE_LIMITS.projectFiles && value.every((file) => record(file) && exactKeys(file, ["path", "language", "maximumCharacters"]) && typeof file.path === "string" && FILE_PATH.test(file.path) && typeof file.language === "string" && LANGUAGES.includes(file.language) && integer(file.maximumCharacters, 1, LEARNING_COURSE_LIMITS.sourceCharactersPerFile)) && new Set(value.map((file) => file.path)).size === value.length;
69
+ }
70
+ function parseLearningProject(course2, value) {
71
+ if (!fileDefinitions(course2.projectFiles) || !record(value) || !exactKeys(value, ["files"]) || !Array.isArray(value.files) || value.files.length !== course2.projectFiles.length) throw new LearningCourseInputError();
72
+ const files = [];
73
+ const seen = /* @__PURE__ */ new Set();
74
+ let characters = 0;
75
+ for (const file of value.files) {
76
+ if (!record(file) || !exactKeys(file, ["path", "source"]) || typeof file.path !== "string" || typeof file.source !== "string" || seen.has(file.path)) throw new LearningCourseInputError();
77
+ const definition = course2.projectFiles.find((candidate) => candidate.path === file.path);
78
+ if (!definition || file.source.length > definition.maximumCharacters || file.source.includes("\0")) throw new LearningCourseInputError();
79
+ characters += file.source.length;
80
+ if (characters > LEARNING_COURSE_LIMITS.sourceCharactersPerProject) throw new LearningCourseInputError();
81
+ seen.add(file.path);
82
+ files.push({ path: file.path, source: file.source });
83
+ }
84
+ return { files: course2.projectFiles.map((definition) => files.find((file) => file.path === definition.path)) };
85
+ }
86
+ function validateLearningCourse(value) {
87
+ const issues = [];
88
+ const add = (code, path) => {
89
+ issues.push({ code, path });
90
+ };
91
+ if (!record(value)) return [{ code: "invalid-manifest", path: "$" }];
92
+ if (!exactKeys(value, [
93
+ "schemaVersion",
94
+ "moduleId",
95
+ "moduleVersion",
96
+ "slug",
97
+ "title",
98
+ "summary",
99
+ "runtimeId",
100
+ "category",
101
+ "estimatedMinutes",
102
+ "completionAssessmentId",
103
+ "projectFiles",
104
+ "starterProject",
105
+ "reference",
106
+ "missions",
107
+ "completionBadge"
108
+ ]) || value.schemaVersion !== "1" || !id(value.moduleId) || !id(value.slug) || !id(value.runtimeId) || !text(value.moduleVersion) || !VERSION.test(value.moduleVersion) || typeof value.category !== "string" || !CATEGORIES.includes(value.category) || !text(value.title, 3, 160) || !text(value.summary, 40, 2e3) || !integer(value.estimatedMinutes, 60, 3600) || !id(value.completionAssessmentId) || !record(value.completionBadge) || !exactKeys(value.completionBadge, ["id", "title"]) || !id(value.completionBadge.id) || !text(value.completionBadge.title, 3, 160)) add("invalid-manifest", "$");
109
+ if (!fileDefinitions(value.projectFiles)) add("invalid-project", "projectFiles");
110
+ else {
111
+ try {
112
+ parseLearningProject({ projectFiles: value.projectFiles }, value.starterProject);
113
+ } catch {
114
+ add("invalid-project", "starterProject");
115
+ }
116
+ }
117
+ if (!Array.isArray(value.reference) || value.reference.length < 1 || value.reference.length > 80 || value.reference.some((entry) => !record(entry) || !exactKeys(entry, ["name", "signature", "description", "example"]) || !text(entry.name) || !text(entry.signature) || !text(entry.description, 20) || !text(entry.example))) add("incomplete-content", "reference");
118
+ if (!Array.isArray(value.missions)) {
119
+ add("mission-count", "missions");
120
+ return issues;
121
+ }
122
+ if (value.missions.length !== LEARNING_COURSE_LIMITS.missions) add("mission-count", "missions");
123
+ if (value.missions.length > LEARNING_COURSE_LIMITS.missions) return issues;
124
+ const seen = /* @__PURE__ */ new Set();
125
+ const checkId = (candidate, path) => {
126
+ if (!id(candidate)) add("invalid-manifest", path);
127
+ else if (seen.has(candidate)) add("duplicate-id", path);
128
+ else seen.add(candidate);
129
+ };
130
+ let minutes = 0;
131
+ value.missions.forEach((mission, missionIndex) => {
132
+ const path = `missions[${missionIndex}]`;
133
+ if (!record(mission)) {
134
+ add("invalid-manifest", path);
135
+ return;
136
+ }
137
+ checkId(mission.id, `${path}.id`);
138
+ checkId(mission.assessmentId, `${path}.assessmentId`);
139
+ if (!exactKeys(mission, ["id", "title", "concepts", "estimatedMinutes", "goals", "assessmentId", "stages", "extension"]) || !text(mission.title, 3, 160) || !integer(mission.estimatedMinutes, 10, 600) || !Array.isArray(mission.concepts) || mission.concepts.length < 1 || mission.concepts.length > 12 || mission.concepts.some((concept) => !text(concept, 2, 120)) || !Array.isArray(mission.goals) || mission.goals.length < 1 || mission.goals.length > 12 || mission.goals.some((goal) => !text(goal, 20, 2e3)) || !text(mission.extension, 30, 2e3)) add("incomplete-content", path);
140
+ if (typeof mission.estimatedMinutes === "number") minutes += mission.estimatedMinutes;
141
+ if (!Array.isArray(mission.stages) || mission.stages.length !== LEARNING_COURSE_LIMITS.stagesPerMission) {
142
+ add("stage-order", `${path}.stages`);
143
+ return;
144
+ }
145
+ mission.stages.forEach((stage, stageIndex) => {
146
+ const stagePath = `${path}.stages[${stageIndex}]`;
147
+ if (!record(stage)) {
148
+ add("invalid-manifest", stagePath);
149
+ return;
150
+ }
151
+ checkId(stage.id, `${stagePath}.id`);
152
+ if (stage.kind !== LEARNING_COURSE_STAGE_ORDER[stageIndex]) add("stage-order", stagePath);
153
+ if (!exactKeys(stage, ["id", "kind", "title", "instruction", "help"]) || !text(stage.title, 3, 160) || !text(stage.instruction, 40) || !text(stage.help, 30) || typeof stage.instruction === "string" && PLACEHOLDER.test(stage.instruction)) add("incomplete-content", stagePath);
154
+ });
155
+ });
156
+ if (minutes !== value.estimatedMinutes) add("invalid-manifest", "estimatedMinutes");
157
+ return issues;
158
+ }
159
+ function parseLearningCourse(value) {
160
+ if (validateLearningCourse(value).length) throw new LearningCourseInputError();
161
+ return structuredClone(value);
162
+ }
163
+
164
+ // src/courses/course-authoring.ts
165
+ function authorCourse(header, missions) {
166
+ const course2 = parseLearningCourse({
167
+ ...header,
168
+ schemaVersion: "1",
169
+ moduleVersion: "2.0.0",
170
+ moduleId: `junior-coder.${header.slug}`,
171
+ runtimeId: `${header.slug}.v2`,
172
+ estimatedMinutes: missions.length * 60,
173
+ completionAssessmentId: `${header.slug}.final`,
174
+ completionBadge: { id: `${header.slug}.completed`, title: `${header.title} creator` },
175
+ missions: missions.map((mission, index) => ({
176
+ id: `${header.slug}.m${index + 1}`,
177
+ title: mission.title,
178
+ concepts: mission.concepts,
179
+ goals: mission.goals,
180
+ estimatedMinutes: 60,
181
+ assessmentId: `${header.slug}.m${index + 1}.assessment`,
182
+ extension: mission.extension,
183
+ stages: LEARNING_COURSE_STAGE_ORDER.map((kind) => ({
184
+ id: `${header.slug}.m${index + 1}.${kind}`,
185
+ kind,
186
+ title: `${kind.charAt(0).toUpperCase()}${kind.slice(1)}: ${mission.title}`,
187
+ instruction: mission.activities[kind][0],
188
+ help: mission.activities[kind][1]
189
+ }))
190
+ }))
191
+ });
192
+ const practice2 = structuredClone(missions.flatMap((mission, index) => ["learn", "predict", "explain"].map((kind) => ({ ...mission.questions[kind], stageId: `${header.slug}.m${index + 1}.${kind}` }))));
193
+ for (const question of practice2) {
194
+ if (question.question.trim().length < 20 || question.feedback.trim().length < 40 || question.choices.length !== 3 || question.choices.some((choice) => choice.trim().length < 2) || new Set(question.choices).size !== 3 || !Number.isInteger(question.correctChoice) || question.correctChoice < 0 || question.correctChoice > 2) throw new Error("Invalid course practice question.");
195
+ }
196
+ return { course: course2, practice: practice2 };
197
+ }
198
+
199
+ // src/courses/rescue-crew-commander.ts
200
+ var { course, practice } = authorCourse({
201
+ slug: "rescue-crew-commander",
202
+ title: "Rescue Crew Commander",
203
+ category: "game",
204
+ summary: "Program a rescue team that shares jobs without duplicating work. Plan routes around rubble, prioritise waiting rescues, budget the return journey and recover when a path changes. Finish a coordinated simulation in which people count as rescued only when they reach the safe base.",
205
+ projectFiles: [{ path: "game.js", language: "javascript", maximumCharacters: 32e3 }],
206
+ starterProject: { files: [{ path: "game.js", source: `function initialState() {
207
+ return {
208
+ crew: ["alpha", "beta", "gamma"].map(id => ({
209
+ id, x: 0, y: 0, energy: 40, cargo: 0,
210
+ jobId: null, path: [], phase: "idle"
211
+ })),
212
+ jobs: [], blocked: [], rescued: 0, steps: 0, status: "ready"
213
+ };
214
+ }
215
+
216
+ function update(state, input) {
217
+ return state;
218
+ }
219
+ ` }] },
220
+ reference: [
221
+ { name: "update", signature: "update(state, input) -> state", description: "Handle start, assign, dispatch, step and restart. Start supplies jobs and blocked cells for a 12 by 10 grid. Assign includes workerId and jobId; dispatch requests automatic assignment. A step can replace blocked with a fresh snapshot.", example: 'if (input.action === "restart") return initialState();' },
222
+ { name: "crew", signature: "Array<{ id, x, y, energy, cargo, jobId, path, phase }>", description: "Three workers begin at base (0,0) with 40 energy and capacity two. Each grid move costs one energy. Phases are idle, outbound, returning and blocked; workers may share cells, but a job belongs to only one worker.", example: "const worker = state.crew.find(member => member.id === input.workerId);" },
223
+ { name: "jobs", signature: "Array<{ id, x, y, priority, people, status }>", description: "Start supplies one to twelve distinct jobs, each with one or two people and priority 1 to 3. Copy them as waiting. Assignment reserves a job; pickup marks collected; delivery at base marks completed. Reserved and completed jobs cannot be assigned again.", example: 'const waiting = state.jobs.filter(job => job.status === "waiting");' },
224
+ { name: "route", signature: "route(start, goal, blocked) -> cell[] | null", description: "Use breadth-first search on the bounded 120-cell grid, visiting neighbours north, east, south, west. Mark cells visited when queued. Return cells after the start through the goal; [] means already there, null means unreachable.", example: "const neighbours = [{ x, y: y - 1 }, { x: x + 1, y }, { x, y: y + 1 }, { x: x - 1, y }];" },
225
+ { name: "assignment", signature: "reserve only a feasible round trip", description: "Assign an idle worker only when outbound and return routes exist and their total step count fits its energy. Copy the outbound path, set jobId and phase outbound, and reserve the job together. Invalid assignment has no partial effects.", example: "const affordable = outbound !== null && homeward !== null && outbound.length + homeward.length <= worker.energy;" },
226
+ { name: "dispatch", signature: "priority descending, then id ascending", description: "Consider idle workers in id order and waiting jobs by descending priority, breaking ties by id. Choose the first feasible job for each worker and reserve it before considering the next worker.", example: "jobs.sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id));" },
227
+ { name: "delivery", signature: "collected is not completed", description: "At a job, take its people into cargo once and plan the return route. Only base arrival adds cargo to rescued, clears the worker, completes its job and restores energy to 40. Never count a pickup as a successful delivery.", example: 'worker.phase = worker.cargo > 0 ? "returning" : "outbound";' },
228
+ { name: "recovery", signature: "fresh blocked cells before each step", description: "Validate the next path cell against the current map. Replan to the job or base when needed, requiring enough energy for the remaining round trip. If no safe route is affordable, keep position, cargo and reservation with phase blocked and try again on a later step.", example: "const next = worker.path[0]; const closed = state.blocked.some(cell => cell.x === next.x && cell.y === next.y);" }
229
+ ]
230
+ }, [
231
+ {
232
+ title: "A crew with a job",
233
+ concepts: ["Records", "Ownership", "State transitions"],
234
+ goals: ["Represent independent workers and jobs with stable identities.", "Reserve one waiting job for one idle worker without partially applying an invalid assignment."],
235
+ extension: "Draw a state diagram for waiting, reserved, collected and completed. Explain which transition requires actual movement rather than a button press.",
236
+ activities: {
237
+ learn: ["Workers and jobs have different identities. A worker can own one job, and a reserved job cannot belong to another worker. Assignment must change worker.jobId, worker.phase and job.status together after all checks pass; selecting a job does not rescue its people.", "Treat the job status as a record of real progress, not just a label used to colour a card."],
238
+ predict: ["Alpha reserves job bridge, then beta tries to reserve bridge before alpha moves. Predict beta's assignment result and bridge's owner. Consider an unknown worker id and decide which records should change.", "A rejected assignment should leave both the worker and job collections exactly as they were."],
239
+ build: ["Handle start by copying supplied jobs as waiting and loading the map. Add assignment validation for an existing idle worker and waiting job. For this first clear-map mission, build a simple adjacent route and reserve the job only when the worker can make the required trip.", "Keep the initial crew objects separate; changing alpha must not also change beta through a shared object reference."],
240
+ run: ["Start the clear-map scenario and select a worker and job from the keyboard-accessible lists. Assign the same job twice and inspect the owner and status readouts. Restart and confirm no reservation remains.", "The lists carry the same selection information as clicking a worker or location on the map."],
241
+ assess: ["Check a valid assignment, an already reserved job, a busy worker and unknown ids. The checks look for one coherent state change and no partial mutation when assignment is rejected.", "A job marked reserved without any worker owning it is a lost task even if the map looks unchanged."],
242
+ inspect: ["If two workers claim the same job, inspect status validation before writing worker.jobId. If a rejected click alters a worker, inspect whether changes happen before all checks finish.", "Compare the job's status and every worker's jobId together to find broken ownership."],
243
+ fix: ["Repair the assignment transition, then repeat valid, duplicate and unknown-id cases. Ensure restart recreates three independent idle workers and clears every job and reservation from the old attempt.", "Preserve successful assignment while rejecting only the cases that violate its requirements."],
244
+ explain: ["Choose why reserving a job and assigning its worker must be one coherent decision. Explain why a completed job is different from a selected destination.", "Ownership coordinates work; it is not proof that the people have reached safety."],
245
+ reward: ["Save Shared jobs. You now coordinate identities and ownership instead of moving one object alone. Next you will replace the simple clear route with a search that can navigate around rubble.", "Keep a duplicate-assignment example for later automatic-dispatch tests."]
246
+ },
247
+ questions: {
248
+ learn: { question: "How many workers may own one reserved job at the same time?", choices: ["Any number", "One", "Every idle worker"], correctChoice: 1, feedback: "Exclusive ownership prevents duplicate pickup and makes each job's progress accountable to one worker." },
249
+ predict: { question: "Beta tries to assign a job already reserved by alpha; what should change?", choices: ["The job gains a second owner", "Alpha silently loses the job", "Nothing; beta's assignment is rejected"], correctChoice: 2, feedback: "The existing reservation remains intact and the invalid second assignment has no partial effects." },
250
+ explain: { question: "Why update reservation and worker ownership together?", choices: ["To avoid orphaned or multiply owned work", "To award rescue points immediately", "To skip route planning"], correctChoice: 0, feedback: "Both sides describe the same assignment. Updating only one can leave a job unavailable or owned inconsistently." }
251
+ }
252
+ },
253
+ {
254
+ title: "Routes around the rubble",
255
+ concepts: ["Breadth-first search", "Visited cells", "Bounded work"],
256
+ goals: ["Find a shortest safe grid route without revisiting cells indefinitely.", "Distinguish already-at-target from unreachable and reject unaffordable round trips."],
257
+ extension: "Compare two equal-length routes and explain how a fixed neighbour order chooses one reproducibly without changing the shortest-path guarantee.",
258
+ activities: {
259
+ learn: ["Breadth-first search explores nearby cells before distant ones. Start a queue with the worker's cell, visit valid unblocked neighbours north, east, south, west, and remember each parent. Mark visited when adding to the queue. With 120 cells, each can be queued at most once.", "Reconstruct the route backwards from the goal using parents, then reverse it and omit the starting cell."],
260
+ predict: ["A direct corridor is blocked but a two-step detour exists. Predict whether a route search should fail immediately or explore neighbours. Then distinguish a worker already at the goal from a goal enclosed by rubble.", "An empty route is a valid arrival. Null represents no route; using the same value for both loses important information."],
261
+ build: ["Write route(start, goal, blocked) using a queue, visited set and parent map. Bound the search to 120 cells, return null when unreachable and [] when already there. Use outbound and return path lengths to validate assignment energy before reserving anything.", "Use a coordinate key such as x+','+y. Reject neighbours outside columns 0\u201311 or rows 0\u20139 before queueing them."],
262
+ run: ["Inspect a straight path, a detour and an enclosed goal. Follow the highlighted search frontier or its text list and compare route length. Start and goal at the same cell should report arrival without consuming energy.", "A search trace is different from the movement path: exploring a cell does not mean the worker physically walks there."],
263
+ assess: ["Check route adjacency, boundaries, rubble avoidance, shortest distance, fixed tie order and termination. Cases include unreachable goals, identical endpoints and a route whose return trip exceeds available energy.", "A path to the job alone is insufficient if the worker cannot also bring people home."],
264
+ inspect: ["If search repeats forever, inspect when visited is set. If the route jumps, inspect parent reconstruction. If a trapped goal looks like arrival, inspect the distinction between null and an empty array.", "Every consecutive path cell must differ by exactly one orthogonal step; diagonal shortcuts are not permitted."],
265
+ fix: ["Repair search or route validation and repeat all three map shapes. Confirm the queue never exceeds the board bound and invalid assignments preserve worker energy and job status.", "Do not increase a search timeout to hide repeated visits. Fix the visited rule that bounds the algorithm."],
266
+ explain: ["Choose why marking a cell visited when it is queued prevents duplicate work. Explain why breadth-first layers find a shortest path when every grid edge costs one move.", "The first discovered path to a cell has the smallest number of equal-cost steps."],
267
+ reward: ["Save Rubble routes. Your crew can now plan around obstacles with bounded, reproducible search. Next you will decide which feasible rescue should be assigned first.", "Keep an unreachable map as a useful failure case for later recovery behaviour."]
268
+ },
269
+ questions: {
270
+ learn: { question: "When should a breadth-first search mark a cell visited?", choices: ["Only after the whole search ends", "Every time it is rediscovered", "When it is added to the queue"], correctChoice: 2, feedback: "Marking at enqueue time prevents several neighbours from adding the same cell repeatedly." },
271
+ predict: { question: "What should route return when start already equals goal?", choices: ["An empty valid path", "Null for unreachable", "An infinite loop"], correctChoice: 0, feedback: "No movement is required, so an empty path correctly describes immediate arrival rather than failure." },
272
+ explain: { question: "Why does breadth-first search find shortest paths on this grid?", choices: ["It guesses the goal's direction", "It explores increasing layers of equal-cost steps", "It always follows only the first corridor"], correctChoice: 1, feedback: "All moves cost one step, so exploring by distance layers reaches a cell through a shortest path first." }
273
+ }
274
+ },
275
+ {
276
+ title: "The next most useful rescue",
277
+ concepts: ["Priority", "Stable ordering", "Feasibility"],
278
+ goals: ["Dispatch idle workers by urgency with deterministic tie-breaking.", "Skip infeasible jobs without losing them or assigning the same rescue twice."],
279
+ extension: "Compare urgency-first and nearest-first schedules on the same map. Describe the trade-off without claiming either policy is best for every real situation.",
280
+ activities: {
281
+ learn: ["Automatic dispatch combines a policy with feasibility checks. Sort waiting jobs by priority descending, then id ascending. For each idle worker in id order, choose the first reachable affordable job. Reserve it before considering the next worker so the team does not duplicate work.", "This is the simulator's stated scheduling policy, not a claim about how real emergency services should make decisions."],
282
+ predict: ["Two jobs share priority three and ids bridge and tower. Predict the tie order. Then make bridge unreachable and decide whether an idle worker should remain unused while tower is feasible.", "Priority decides order of consideration. Feasibility still determines whether a candidate can actually be assigned."],
283
+ build: ["Add dispatch handling that considers idle workers and a sorted copy of waiting jobs. Reuse your assignment checks instead of implementing a second weaker route rule. Skip infeasible candidates while leaving them waiting for future map changes.", "Do not sort a shared collection in place if another part of your project relies on its existing display order."],
284
+ run: ["Dispatch three workers into a mix of urgent, equal-priority and blocked jobs. Inspect each assignment and explain its place in the order. Repeat from the same state to verify that the same choices are made.", "A reproducible schedule makes it easier to compare resource use and detect duplicate reservations."],
285
+ assess: ["Check priority order, id ties, worker order, infeasible-job skipping and unique ownership across one dispatch action. Repeating dispatch with all workers busy should change nothing.", "An unreachable high-priority job should stay visible as waiting; deleting it would falsely make the task disappear."],
286
+ inspect: ["If workers choose different jobs on replay, inspect tie-breaking. If two share a job, inspect when reservation occurs. If a blocked urgent job stops all work, inspect whether the loop continues to other candidates.", "Trace candidate consideration separately from successful assignment so skipped work remains explainable."],
287
+ fix: ["Repair ordering or reservation timing, then rerun a schedule with a blocked highest-priority job. Confirm manual assignment still applies the same safety and affordability rules as automatic dispatch.", "Reuse one assignment operation to keep manual and automatic paths consistent."],
288
+ explain: ["Choose why priority and feasibility are different decisions. Explain why deterministic ties help you test the scheduler without implying that a simple priority number captures every possible need.", "A useful model states its policy and limitations rather than hiding them inside accidental array order."],
289
+ reward: ["Save Useful dispatch. Your crew now shares work according to an explicit policy. Next the simulation will distinguish collecting people from delivering them safely with enough energy for the return.", "Keep a mixed-priority map to compare any later scheduling experiments."]
290
+ },
291
+ questions: {
292
+ learn: { question: "What should dispatch do after finding an unreachable highest-priority job?", choices: ["Consider the next feasible waiting job", "Delete the unreachable rescue", "Assign every worker to it anyway"], correctChoice: 0, feedback: "The job remains waiting while the scheduler looks for feasible work that another worker can actually complete." },
293
+ predict: { question: "Equal-priority jobs bridge and tower are both feasible; which is considered first?", choices: ["Whichever is drawn last", "Bridge by ascending id", "A random choice each time"], correctChoice: 1, feedback: "The documented tie-break uses ascending stable ids, making an otherwise equal choice reproducible." },
294
+ explain: { question: "Why reserve a chosen job before dispatch considers the next worker?", choices: ["To award all rescue points early", "To erase the route", "To keep the next worker from choosing the same work"], correctChoice: 2, feedback: "Immediate reservation updates the available work set and preserves exclusive job ownership across the team." }
295
+ }
296
+ },
297
+ {
298
+ title: "Bring everyone home",
299
+ concepts: ["Resource budget", "Pickup versus delivery", "Conservation"],
300
+ goals: ["Spend one energy per actual move and retain people as cargo until base arrival.", "Count each delivered person once and complete its job only after safe return."],
301
+ extension: "Compare two safe routes with different lengths and calculate their energy margins. Explain why a route with no spare energy is more vulnerable to a later detour.",
302
+ activities: {
303
+ learn: ["A worker's round trip includes reaching the job and returning to base. Each actual grid move costs one energy. Pickup transfers one or two people to cargo and marks the job collected; only arrival at (0,0) adds cargo to rescued and completes the job.", "The rescued counter represents delivered people. A worker stranded with cargo has not yet achieved that outcome."],
304
+ predict: ["A route takes seven steps to the job and seven home. Predict its total energy cost and whether a worker with thirteen energy may accept it. At pickup, decide which of cargo, rescued and job status should change.", "Budget both directions before assignment. Collection and completion are separate transitions with separate evidence."],
305
+ build: ["Move each active worker at most one adjacent path cell per step and subtract one energy. At the job, pick up once, mark collected and route home. At base, add cargo to rescued, mark completed, clear ownership/path/cargo, restore forty energy and set idle.", "Make arrivals idempotent: another step while already idle at base must not repeat the delivery count."],
306
+ run: ["Follow one worker from assignment through pickup to return. Watch energy, cargo, job status and rescued at each arrival. Pause after pickup to verify the rescued counter has not yet increased.", "Use the worker details to distinguish people being carried from people already safe at base."],
307
+ assess: ["Check exact move cost, insufficient round-trip energy, one-time pickup, cargo capacity, one-time delivery and recharge at base. Repeated idle steps and duplicate assignment must not create extra rescues.", "Conservation means each job's people are waiting, carried or delivered; they cannot be in two categories at once."],
308
+ inspect: ["If rescue totals jump at pickup, inspect the completion transition. If repeated base steps add points, inspect cargo clearing and job status. If energy goes negative, inspect movement validation and assignment affordability.", "Track one job's people through every state rather than trusting the final counter alone."],
309
+ fix: ["Repair the transition that first violates conservation and replay the same round trip. Verify a legitimate delivery still counts, then check a rejected low-energy assignment and a worker with no job at base.", "Keep energy spending tied to committed movement; search exploration and rejected steps do not consume it."],
310
+ explain: ["Choose why pickup cannot count as a completed rescue. Explain how a full-trip budget and one-time delivery transition make the result meaningful rather than a manually increased score.", "The goal is a completed journey to safety, not merely reaching a map marker."],
311
+ reward: ["Save Safe return. Your simulation now accounts for time, movement, energy and actual deliveries. Next you will respond to rubble changes without walking through a newly blocked cell or losing the worker's cargo.", "Keep the thirteen-energy example as a regression case for future route changes."]
312
+ },
313
+ questions: {
314
+ learn: { question: "When should people be added to rescued?", choices: ["At assignment", "At their delivery to base", "Every step while carried"], correctChoice: 1, feedback: "The counter records people who have completed the return journey to the safe base, not merely been selected or collected." },
315
+ predict: { question: "A seven-step outbound and seven-step return route requires how much energy?", choices: ["Seven", "Thirteen", "Fourteen"], correctChoice: 2, feedback: "Both directions spend energy, so the complete trip costs seven plus seven moves." },
316
+ explain: { question: "Why clear cargo when recording a delivery?", choices: ["To prevent later base steps counting the same people again", "To remove their completed job history", "To make all other workers stop"], correctChoice: 0, feedback: "Clearing cargo records that the people have left the worker and entered the delivered count exactly once." }
317
+ }
318
+ },
319
+ {
320
+ title: "When the route changes",
321
+ concepts: ["Fresh observations", "Replanning", "Safe waiting"],
322
+ goals: ["Check the next move against current rubble and replan within remaining energy.", "Wait safely without dropping cargo or reservations when no feasible route exists."],
323
+ extension: "Compare a temporary closure with a permanent detour. Explain which worker states must be retained while waiting and how reopening a path should resume the journey.",
324
+ activities: {
325
+ learn: ["A saved path describes an earlier map. Before each move, read the current blocked cells. If the next cell is closed, search again from the worker's actual position. Recheck the remaining energy budget; if no safe affordable route exists, keep the worker still with phase blocked.", "Blocked is an explicit recoverable state. It does not mean erase the job, teleport home or count its cargo as delivered."],
326
+ predict: ["A returning worker carries two people when its next cell closes. Predict what happens if an affordable detour exists, and if every route home is closed. Then reopen a corridor on the next step.", "The worker retains its position, cargo, job ownership and energy while it cannot move. A fresh map can make progress possible later."],
327
+ build: ["Load a fresh blocked snapshot before movement. Revalidate the next cell, replan toward the job or base as appropriate and verify enough remaining energy. If infeasible, clear the stale path and wait blocked; retry from current state on a later step.", "For outbound workers include the future homeward path in the budget. Returning workers need only the remaining trip to base."],
328
+ run: ["Close a corridor ahead of an outbound worker and then a returning one. Inspect the new route or blocked status. Reopen a path and step again, checking that energy is spent only when a move actually occurs.", "A clear blocked-status message helps explain why a worker has stopped instead of presenting a silent failure."],
329
+ assess: ["Check changed next cells, affordable and unaffordable detours, complete closure and reopening. No worker may enter rubble, lose cargo, duplicate a job or spend energy while waiting.", "A stale cached path cannot override the current map even if it was valid when the job was first assigned."],
330
+ inspect: ["If workers walk through rubble, inspect when the map is refreshed. If reopening does nothing, inspect whether blocked workers are reconsidered. If people disappear, inspect mutations made in the failed-replan branch.", "Failure handling should preserve valuable state while changing only the route and explicit status needed to explain the pause."],
331
+ fix: ["Repair replanning and replay closure, waiting and reopening as one sequence. Preserve the original job ownership and eventual one-time delivery, then rerun an unchanged-map round trip to ensure the ordinary path remains efficient.", "Do not retry search endlessly inside one step. One bounded search and a visible waiting state are sufficient until new input arrives."],
332
+ explain: ["Choose why a previous successful plan is not permission to ignore new observations. Explain how bounded retries on later steps keep the simulation responsive while allowing recovery.", "A plan is useful only while its assumptions still hold; a blocked state makes changed assumptions visible."],
333
+ reward: ["Save Adaptive crew. Your workers can now recover from changing routes without corrupting their progress. The final mission combines shared jobs, search, priorities, resources and recovery into a complete rescue operation.", "Keep a close-and-reopen scenario to verify that future changes do not lose carried people."]
334
+ },
335
+ questions: {
336
+ learn: { question: "What should validate a worker's next move?", choices: ["Only the map from initial assignment", "The age of its save slot", "The current blocked-cell snapshot"], correctChoice: 2, feedback: "New obstacles can invalidate an old path, so the next move must use fresh world information." },
337
+ predict: { question: "A returning worker has no safe affordable route; what should happen?", choices: ["Wait with cargo and ownership preserved", "Teleport home and count success", "Drop the people and delete the job"], correctChoice: 0, feedback: "Safe waiting keeps the real state intact until another observation makes a valid route possible." },
338
+ explain: { question: "Why retry a blocked route on a later step instead of looping forever now?", choices: ["To guarantee the map never changes", "To keep work bounded while waiting for new information", "To abandon the assigned rescue"], correctChoice: 1, feedback: "The current map has already been checked. A bounded attempt preserves responsiveness and a later snapshot may provide a new route." }
339
+ }
340
+ },
341
+ {
342
+ title: "A coordinated rescue",
343
+ concepts: ["Integration", "Conservation checks", "Explainable decisions"],
344
+ goals: ["Coordinate three workers through every job with exclusive ownership and safe return.", "Finish only when all people are delivered and the crew is safely idle at base."],
345
+ extension: "Try another scheduling policy in a separate save, document its intended benefit and compare it on the same maps while preserving all ownership, energy and safety rules.",
346
+ activities: {
347
+ learn: ["A complete rescue operation needs both useful choices and trustworthy state. The final condition requires every job completed, all cargo delivered and all workers idle at base. A temporarily empty waiting list can mean that work is still reserved or carried, not finished.", "Check conservation throughout: original job people equal people still waiting or assigned, people carried, and people safely delivered."],
348
+ predict: ["All jobs are reserved and the waiting list is empty, but one worker is blocked while carrying people. Predict whether the operation is complete. Identify the evidence that would be needed to change that result.", "Look at job completion, cargo, position and worker phase rather than only the list of available assignments."],
349
+ build: ["Combine assignment, bounded route search, dispatch, movement, arrival and recovery into explicit operations. Add the final won condition only after deliveries resolve. Keep restart separate so it creates fresh crew, jobs and counters without altering saved project code.", "A result flag should follow the simulated evidence. Do not infer completion from how many times the dispatch button was pressed."],
350
+ run: ["Complete a rescue using keyboard worker/job selection and another through map controls. Inspect a mixed-priority dispatch, close and reopen a route, pause during a return and finish delivery. Compare every worker and job in the text overview.", "The overview must explain idle, working and blocked states without requiring motion or colour to convey the difference."],
351
+ assess: ["Run the final saved-source scenarios with duplicate assignment, blocked high-priority jobs, limited energy, returning cargo and simultaneous deliveries. Verify exclusive ownership, bounded search and conservation throughout, not just at the final counter.", "A final total can be accidentally correct even when intermediate steps duplicated or lost people. The trace checks the whole journey."],
352
+ inspect: ["Locate the first broken invariant in a failing operation and trace its job and worker ids. Decide whether the cause is scheduling, route validity, resource spending or an arrival transition before editing the code.", "Explain the decision with state evidence: which requirement failed and which records should have remained unchanged."],
353
+ fix: ["Repair the responsible operation and replay its focused scenario plus a full ordinary rescue. Keep search bounded, retain blocked cargo and assess the exact newly saved source before completing the course.", "Avoid hard-coding successful routes for known maps. The final project should respond to supplied job and rubble data."],
354
+ explain: ["Choose what establishes a completed rescue and describe one limitation of this simplified scheduling model. Explain how exclusive ownership and conservation checks made a complicated team easier to reason about.", "The simulator teaches programming concepts; its simple priorities and energy model are not real-world emergency planning advice."],
355
+ reward: ["Save Coordinated rescue and finish the final assessment. You have built a team simulation with shared work, route search, resources and recoverable failures. Replay any mission or keep an alternative scheduler in another named save.", "Earned course completion remains while you explore; preserve the tested project so you can compare and recover."]
356
+ },
357
+ questions: {
358
+ learn: { question: "Which evidence establishes that the rescue operation is complete?", choices: ["All jobs completed and the crew safely idle at base with no cargo", "Only an empty waiting list", "A fixed number of dispatch clicks"], correctChoice: 0, feedback: "Completion follows actual delivery and safe crew return, not merely the absence of currently unassigned work." },
359
+ predict: { question: "No jobs are waiting, but a worker is blocked with cargo; is the operation complete?", choices: ["Yes, every job was selected", "No, the carried people still need delivery", "Yes, blocked means rescued"], correctChoice: 1, feedback: "Reserved or collected work can remain unfinished. People carried by a blocked worker have not reached the base." },
360
+ explain: { question: "Why check conservation during the trace as well as at the final total?", choices: ["To ignore worker identities", "To make every route longer", "To catch temporary duplication or loss hidden by a coincidental final count"], correctChoice: 2, feedback: "A correct final number alone does not show that each person followed one valid path from waiting through pickup to delivery." }
361
+ }
362
+ }
363
+ ]);
364
+ // Annotate the CommonJS export names for ESM import in node:
365
+ 0 && (module.exports = {
366
+ course,
367
+ practice
368
+ });
369
+ //# sourceMappingURL=rescue-crew-commander.cjs.map