@plasius/learning 0.7.0 → 0.8.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.
- package/README.md +21 -0
- package/dist/chunk-UNPCTBNK.js +43 -0
- package/dist/chunk-UNPCTBNK.js.map +1 -0
- package/dist/courses/adventure-mission-planner.cjs +436 -0
- package/dist/courses/adventure-mission-planner.cjs.map +1 -0
- package/dist/courses/adventure-mission-planner.d.cts +6 -0
- package/dist/courses/adventure-mission-planner.d.ts +6 -0
- package/dist/courses/adventure-mission-planner.js +210 -0
- package/dist/courses/adventure-mission-planner.js.map +1 -0
- package/dist/courses/creature-care-dashboard.cjs +425 -0
- package/dist/courses/creature-care-dashboard.cjs.map +1 -0
- package/dist/courses/creature-care-dashboard.d.cts +6 -0
- package/dist/courses/creature-care-dashboard.d.ts +6 -0
- package/dist/courses/creature-care-dashboard.js +199 -0
- package/dist/courses/creature-care-dashboard.js.map +1 -0
- package/dist/courses/robot-mission-control.cjs +428 -0
- package/dist/courses/robot-mission-control.cjs.map +1 -0
- package/dist/courses/robot-mission-control.d.cts +6 -0
- package/dist/courses/robot-mission-control.d.ts +6 -0
- package/dist/courses/robot-mission-control.js +202 -0
- package/dist/courses/robot-mission-control.js.map +1 -0
- package/package.json +17 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/courses/creature-care-dashboard.ts","../../src/mission-authoring.ts","../../src/course-contracts.ts","../../src/courses/course-authoring.ts","../../src/courses/web-course-authoring.ts"],"sourcesContent":["import { authorCourse } from \"./course-authoring.js\";\nimport { webProjectFiles, webReferences, webStarterCss } from \"./web-course-authoring.js\";\n\nexport const { course, practice } = authorCourse({\n slug: \"creature-care-dashboard\", title: \"Creature Care Dashboard\", category: \"web-app\",\n summary: \"Create an accessible dashboard for a fictional creature. Connect labelled care actions to bounded state, model time without browser timers, distinguish rest from pause and show an honest activity history. Finish a responsive three-file application with readable status, safe reset and evidence for its edge cases.\",\n projectFiles: webProjectFiles,\n starterProject: { files: [\n { path: \"index.html\", source: `<main>\n <h1>Creature Care Dashboard</h1><p>Care for a fictional creature in a simulated habitat.</p>\n <section aria-labelledby=\"creature-heading\"><h2 id=\"creature-heading\" data-text=\"name\"></h2>\n <p data-text=\"modeText\"></p><p data-text=\"clockText\"></p>\n <label for=\"food\">Food</label><meter id=\"food\" min=\"0\" max=\"100\" data-value=\"food\" data-label=\"foodLabel\"></meter><p data-text=\"foodLabel\"></p>\n <label for=\"energy\">Energy</label><meter id=\"energy\" min=\"0\" max=\"100\" data-value=\"energy\" data-label=\"energyLabel\"></meter><p data-text=\"energyLabel\"></p>\n <label for=\"joy\">Joy</label><meter id=\"joy\" min=\"0\" max=\"100\" data-value=\"joy\" data-label=\"joyLabel\"></meter><p data-text=\"joyLabel\"></p>\n <div class=\"actions\"><button type=\"button\" data-action=\"feed\" data-disabled=\"feedDisabled\">Feed</button><button type=\"button\" data-action=\"play\" data-disabled=\"playDisabled\">Play</button><button type=\"button\" data-action=\"rest\" data-disabled=\"restDisabled\">Rest</button><button type=\"button\" data-action=\"wake\" data-disabled=\"wakeDisabled\">Wake</button></div>\n </section>\n <div class=\"actions\"><button type=\"button\" data-action=\"resume\">Resume simulation</button><button type=\"button\" data-action=\"pause\">Pause simulation</button><button type=\"button\" data-action=\"reset\">Reset habitat</button></div>\n <section aria-labelledby=\"history-heading\"><h2 id=\"history-heading\">Care history</h2><p data-text=\"historySummary\"></p><ol><li data-repeat=\"history\" data-text=\"text\"></li></ol></section>\n <p role=\"status\" data-text=\"message\"></p>\n</main>` },\n { path: \"app.css\", source: webStarterCss },\n { path: \"app.js\", source: `function initialState() {\n return { name: \"Moss\", draftName: \"Moss\", nameError: \"\", food: 60, energy: 70, joy: 50,\n mode: \"awake\", paused: true, elapsed: 0, history: [], nextEventId: 1, message: \"Resume when you are ready to care for Moss.\" };\n}\nfunction update(state, input) {\n if (input.type === \"reset\") return initialState();\n return JSON.parse(JSON.stringify(state));\n}\nfunction view(state) {\n return { name: state.name, draftName: state.draftName, nameError: state.nameError, nameInvalid: state.nameError !== \"\",\n food: state.food, energy: state.energy, joy: state.joy,\n foodLabel: \"Food \" + Math.round(state.food) + \" of 100\", energyLabel: \"Energy \" + Math.round(state.energy) + \" of 100\",\n joyLabel: \"Joy \" + Math.round(state.joy) + \" of 100\", modeText: state.mode + (state.paused ? \", paused\" : \", running\"),\n clockText: Math.floor(state.elapsed) + \" seconds simulated\", feedDisabled: true, playDisabled: true, restDisabled: true,\n wakeDisabled: true, resumeDisabled: false, pauseDisabled: true, history: [], historySummary: \"No care actions yet.\", message: state.message };\n}\n` },\n ] },\n reference: [...webReferences,\n { name: \"initialState\", signature: \"initialState() → fresh habitat\", description: \"Start with name/draftName='Moss', nameError='', food=60, energy=70, joy=50, mode='awake', paused=true, elapsed=0, history=[], nextEventId=1 and useful message. Food, energy and joy always remain within 0–100. The model is fictional game behaviour, not advice about real animals.\", example: '{ food: 60, energy: 70, joy: 50, mode: \"awake\", paused: true }' },\n { name: \"care rules\", signature: \"feed/play/rest/wake\", description: \"Successful actions require !paused. Feed also requires awake and food<100; add 20 food capped at 100. Play requires awake, food>=10, energy>=15 and joy<100; subtract 10 food and 15 energy, add 20 joy capped at 100. Rest changes awake to asleep; wake changes asleep to awake. Refused/repeated ineligible actions change only message. Each successful care/mode action records exactly one history event; no action mutates input state.\", example: 'if (next.paused || next.mode !== \"awake\" || next.energy < 15 || next.food < 10 || next.joy >= 100) return { ...next, message: \"Play needs an awake, running creature with enough food and energy.\" };' },\n { name: \"tick\", signature: '{ type: \"tick\", dt: seconds }', description: \"The host supplies finite dt from 0 to 1 inclusive; invalid/negative/oversized values preserve state. When running, advance by min(dt,3600-elapsed); decrease food by 1/second and joy by 0.5/second, and change energy by -0.75/second awake or +3/second asleep. Clamp all needs to 0–100. At elapsed=3600 pause and explain that reset starts another session. Ignore ticks while paused. No Date, setInterval or device-clock access is used.\", example: 'const seconds = Math.min(input.dt, 3600 - next.elapsed); next.food = Math.max(0, next.food - seconds);' },\n { name: \"pause and reset\", signature: \"resume/pause/reset\", description: \"resume clears paused only when elapsed<3600; pause sets it immediately without changing mode, needs or history. Repeating either action changes only feedback. Pause freezes both time and care actions; rest allows time to continue while energy recovers. reset creates a fresh paused habitat with empty history and original values, independently of course completion or account source saves.\", example: 'if (input.type === \"pause\") return { ...next, paused: true, message: \"Simulation paused.\" };' },\n { name: \"history\", signature: \"[{id,action,at}] newest first\", description: \"Append one event only after a successful feed/play/rest/wake transition, using id='care-'+nextEventId, action and at=elapsed; then increment nextEventId. Store newest first and keep the latest 20 entries. nextEventId is an integer 1–1000000; refuse a care action if no ID remains. Ticks, naming, pause and refused actions do not create entries. IDs are never reused when old entries leave the list.\", example: 'next.history = [{ id: \"care-\" + next.nextEventId, action: input.type, at: next.elapsed }, ...next.history].slice(0, 20); next.nextEventId += 1;' },\n { name: \"name\", signature: 'field{name:\"name\",value} then rename', description: \"A string name field updates only draftName, at most 30 characters, and clears nameError. rename trims it and accepts 2–30 characters without control characters; success changes name, normalises draftName and clears errors, while failure keeps name and draft and sets useful error text. Naming is allowed while paused and makes no care-history entry. Use fictional names without personal information.\", example: '<input id=\"name\" name=\"name\" type=\"text\" maxlength=\"30\" aria-describedby=\"name-error\" data-value=\"draftName\" data-invalid=\"nameInvalid\">' },\n { name: \"view\", signature: \"view(state) → needs, permissions and readable history\", description: \"Return name/draftName/nameError/nameInvalid, numeric food/energy/joy plus rounded text labels, modeText and clockText, feedDisabled/playDisabled/restDisabled/wakeDisabled/resumeDisabled/pauseDisabled reflecting actual guards, history [{id,text}], historySummary and message. Read state without mutation. Include mode and paused in text; numeric meters need labels. Do not put every tick in a live region; announce explicit actions and failures instead.\", example: 'const foodLabel = \"Food \" + Math.round(state.food) + \" of 100\";' },\n ],\n}, [\n {\n title: \"Meet the creature in data\", concepts: [\"State modelling\", \"Semantic meters\", \"Responsive styling\"],\n goals: [\"Present the creature's initial needs truthfully with text and labelled meters.\", \"Make a readable three-file dashboard before implementing care actions.\"],\n extension: \"Try an alternative habitat theme in a separate save. Preserve the numeric meanings and readable labels while changing its visual character.\",\n activities: {\n learn: [\"The creature is a fictional model with food, energy and joy from 0–100. JavaScript owns those values, HTML names their meaning and CSS arranges them. A meter needs an accessible name and nearby text so its meaning is not conveyed only by colour or length.\", \"The initial paused state gives people time to understand the page before the simulation begins.\"],\n predict: [\"Predict the three initial meter values and their rounded text labels from food 60, energy 70 and joy 50. Then predict whether simply calling view should change any need or elapsed time.\", \"Reading the dashboard is a projection of state, not an event that feeds or tires the creature.\"],\n build: [\"Review the semantic main/heading/section structure and complete truthful initial view data. Style the habitat and need indicators for 320px, large text and both themes, keeping meter values and readable labels connected to the same state.\", \"Keep all important status visible in words; a green bar by itself cannot explain whether it represents food, energy or joy.\"],\n run: [\"Inspect the paused habitat with keyboard navigation, zoom and a screen reader. Compare each meter's accessible label with its value and the visible text, then reset and confirm the same starting values.\", \"The care controls are intentionally unfinished while this first mission establishes an honest initial dashboard.\"],\n assess: [\"Check fresh initial state, bounded needs, truthful labels, one main landmark and nonmutating view. Confirm controls have meaningful names and the layout remains readable without depending on motion.\", \"This milestone does not require later time or care mechanics, but it does require an accurate representation of the initial model.\"],\n inspect: [\"If text and meters disagree, inspect whether both derive from the same field. If an indicator has no announced name, inspect its label association instead of adding another colour cue.\", \"A visually attractive indicator can still be ambiguous when read without its surrounding layout.\"],\n fix: [\"Repair the projection, label or responsive rule and repeat the initial-state checks at 320px. Preserve visible focus outlines and avoid fixed heights that clip text when zoomed.\", \"Let content determine the height of panels so readable text is not traded away for a rigid card shape.\"],\n explain: [\"Explain why a meter needs both a numeric model and a meaningful name. Choose the design that communicates the same state to people using different ways of reading the page.\", \"Accessible presentation describes the underlying model clearly rather than adding a separate inconsistent version of it.\"],\n reward: [\"Save Honest habitat. Your creature's state can now be understood through labelled values and a readable page. Next you will make its care actions change that model.\", \"Keep this paused baseline for comparing changes caused by each later action.\"],\n },\n questions: {\n learn: { question: \"What does a labelled meter communicate beyond a coloured bar?\", choices: [\"Which need the value represents\", \"A hidden timer\", \"A new care action\"], correctChoice: 0, feedback: \"A meaningful label explains the quantity being displayed even when colour or visual position is unavailable.\" },\n predict: { question: \"What should calling view do to energy=70?\", choices: [\"Reduce it automatically\", \"Leave it at 70\", \"Reset it to 100\"], correctChoice: 1, feedback: \"The display function reads state without advancing time or performing actions, so viewing the dashboard preserves energy.\" },\n explain: { question: \"Which display avoids relying only on colour?\", choices: [\"Three unnamed coloured blocks\", \"An unlabeled animation\", \"Named meters with readable values and status text\"], correctChoice: 2, feedback: \"Labels, values and status text communicate the model through more than a single visual signal.\" },\n },\n },\n {\n title: \"Make care actions matter\", concepts: [\"Guarded actions\", \"Clamping\", \"Detached updates\"],\n goals: [\"Implement meaningful feed and play transitions with explicit prerequisites.\", \"Refuse unavailable actions without partial effects and keep control states truthful.\"],\n extension: \"Write a short explanation of why the creature cannot play in each refused case. Keep messages useful without changing the resource rules.\",\n activities: {\n learn: [\"Feed and play spend or replenish specific resources. Feed adds 20 food up to 100. Play spends 10 food and 15 energy to add 20 joy up to 100. Both need an awake running creature, and checking prerequisites before any change prevents a refused action from partially spending resources.\", \"A disabled button helps explain availability, but update must enforce the same guard even if an action arrives directly.\"],\n predict: [\"Predict feed at food 90 and play at food 10, energy 15, joy 90. Compare the exact-boundary play with energy 14 and explain which fields may change in the refused case.\", \"Clamping the gain and checking the costs are separate operations; a generous reward must not permit a negative resource balance.\"],\n build: [\"Implement resume and pause guards, then feed/play using detached next state and exact prerequisites. Derive feedDisabled and playDisabled from the same rules. Add useful success/refusal messages while keeping history for a later mission.\", \"At this milestone the host assesses resource behaviour; the later history lesson adds receipt tracking without changing these care rules.\"],\n run: [\"Resume, feed near the maximum and play until a resource prevents another turn. Pause and try care again. Compare enabled controls, messages and resource changes with the predictions.\", \"Use the same action sequence with keyboard and touch so both input paths exercise the same state transition.\"],\n assess: [\"Check gains, costs, upper and lower bounds, exact eligibility thresholds, paused/asleep guards, unknown actions and input immutability. Confirm refused actions change only feedback and never partially spend food or energy.\", \"Direct-action scenarios verify guards independently from whatever the current button appearance suggests.\"],\n inspect: [\"If a rejected play drains energy, inspect the order of mutation and guard checks. If a full food meter still accepts feeding, inspect the food<100 condition in both update and view.\", \"The interface and transition function must agree on availability; neither should have an unrelated copy of the rule.\"],\n fix: [\"Repair the earliest wrong resource transition and replay threshold cases on each side of eligibility. Verify that repeated requests cannot push values outside 0–100 and that the current source preserves input objects.\", \"Clone nested state before changing it so previous states remain useful for comparison and later evidence.\"],\n explain: [\"Explain why resource costs are checked before applying any effect and why a disabled button is not the sole guard. Choose the result for a play request with insufficient energy.\", \"The transition function is responsible for correctness even when an event does not originate from the visible control.\"],\n reward: [\"Save Meaningful care. Feeding and play now have bounded, understandable consequences. Next you will make the fictional habitat evolve through supplied simulated time.\", \"Keep the exact-threshold case because small comparison mistakes often hide at resource boundaries.\"],\n },\n questions: {\n learn: { question: \"Where must care prerequisites be enforced?\", choices: [\"Only in CSS\", \"In update as well as truthful control state\", \"Only in the button label\"], correctChoice: 1, feedback: \"The model must reject invalid direct actions while the interface accurately explains which actions are currently available.\" },\n predict: { question: \"What is food after feeding from 90?\", choices: [\"110\", \"90\", \"100\"], correctChoice: 2, feedback: \"Feeding adds twenty but clamps the result to the documented maximum of one hundred.\" },\n explain: { question: \"What should play do with energy 14?\", choices: [\"Preserve needs and explain refusal\", \"Spend food anyway\", \"Set energy to -1\"], correctChoice: 0, feedback: \"Insufficient energy makes the whole action ineligible, so resources stay unchanged and feedback explains the refusal.\" },\n },\n },\n {\n title: \"Let simulated time pass\", concepts: [\"Time steps\", \"Rates\", \"Deterministic simulation\"],\n goals: [\"Advance needs using bounded supplied time steps rather than browser timers.\", \"Clamp values and stop the session at its exact duration limit.\"],\n extension: \"Compare one one-second tick with two half-second ticks away from boundaries. Explain why the same elapsed time should produce the same resource changes.\",\n activities: {\n learn: [\"The host supplies tick actions with finite dt between 0 and 1 seconds. While running, food falls by 1 per second, joy by 0.5 and awake energy by 0.75. Multiply each rate by dt, clamp needs and cap total elapsed time at 3600 seconds.\", \"No browser timer or Date call is needed; supplied time makes the same action sequence repeatable in assessment.\"],\n predict: [\"Predict food, joy and energy after half a second awake from the initial needs. Then predict a tick of 2 seconds, a negative tick and a tick while paused.\", \"Invalid time inputs preserve the model; a large delayed frame is not permission to bypass the bounded tick contract.\"],\n build: [\"Implement tick validation and elapsed advancement, limiting the last step to the remaining session time. Apply awake decay rates and clamp needs. At 3600 pause and show a reset instruction, with resumeDisabled remaining true until reset.\", \"Use the same effective seconds for elapsed and every rate so the final partial step remains coherent.\"],\n run: [\"Resume and compare a recorded sequence of half-second and one-second ticks. Pause between ticks, then use the near-session-end scenario to check the final fractional step and automatic pause.\", \"The host controls simulation pace; changing real browser frame timing must not alter the meaning of one supplied tick.\"],\n assess: [\"Check finite dt boundaries, rejected oversized/negative inputs, paused preservation, decay rates, zero clamps, partitioned time and the exact 3600-second stop. Confirm ticks never create care-history records.\", \"The timer display may round elapsed time, but assessment checks the underlying numeric state.\"],\n inspect: [\"If decay depends on frame count, look for subtracting a fixed amount instead of rate times dt. If the last step overshoots, compare effective seconds used for elapsed and needs.\", \"A visible whole-second clock can hide fractional errors; inspect the underlying values when a prediction differs.\"],\n fix: [\"Repair time scaling or boundary handling and replay the same deterministic tick sequence. Verify that zero time has no effects and that repeated ticks after automatic pause leave the finished session unchanged.\", \"Stopping the clock is a state rule, not just hiding its visible number.\"],\n explain: [\"Explain why supplied dt supports repeatable tests and why elapsed time has a finite cap. Distinguish simulated time from how long someone leaves the browser tab open.\", \"The model advances only through accepted actions, making its behaviour independent of an uncontrolled device clock.\"],\n reward: [\"Save Measured time. The habitat now evolves predictably within fixed limits. Next you will combine time with rest and deliberate pause/recovery behaviour.\", \"Keep the fractional final-step case for later changes to energy recovery and status feedback.\"],\n },\n questions: {\n learn: { question: \"How much food is lost during an accepted half-second tick?\", choices: [\"One unit regardless of dt\", \"Two units\", \"Half a unit\"], correctChoice: 2, feedback: \"Food decays at one unit per second, so multiplying by half a second gives a loss of one half.\" },\n predict: { question: \"What should a tick with dt=2 do?\", choices: [\"Preserve state because it exceeds the allowed step\", \"Advance two seconds anyway\", \"Reset the habitat\"], correctChoice: 0, feedback: \"The input contract allows steps only up to one second, so an oversized tick is rejected without effects.\" },\n explain: { question: \"Why supply time as an input instead of reading Date?\", choices: [\"To hide elapsed time\", \"To make replay and assessment deterministic\", \"To make every action random\"], correctChoice: 1, feedback: \"Explicit time inputs let the same initial state and action sequence produce reproducible results across hosts.\" },\n },\n },\n {\n title: \"Rest, pause and recover\", concepts: [\"State machines\", \"Independent modes\", \"Fresh reset\"],\n goals: [\"Distinguish sleeping recovery from pausing the whole simulation.\", \"Implement guarded mode changes and fresh reset without losing course progress.\"],\n extension: \"Sketch a state diagram for awake/asleep crossed with running/paused. Explain which transitions affect mode and which affect the simulation clock.\",\n activities: {\n learn: [\"Rest changes awake to asleep while the simulation keeps running. Asleep energy recovers by 3 per second; food and joy still decay. Pause freezes all time and care actions without changing awake/asleep mode. Reset starts a fresh paused habitat independently of earned course completion.\", \"Two independent state fields express these different ideas more clearly than one ambiguous status word.\"],\n predict: [\"Predict energy after two accepted one-second ticks asleep from 70. Compare that with the same ticks paused asleep, then predict wake while paused and resume afterwards.\", \"A paused creature cannot change care mode until resumed; resuming preserves whichever mode was already selected.\"],\n build: [\"Implement rest/wake guards, asleep energy recovery and truthful control permissions. Preserve needs and mode when pausing, preserve mode when resuming, and reset all preview state through a new initialState result.\", \"Refused repeated rest or wake changes only feedback; it must not manufacture a second successful transition.\"],\n run: [\"Resume, rest, advance time, pause, advance attempted time, resume and wake. Compare energy, food, joy and elapsed after each action. Reset and verify original values, empty history and paused mode.\", \"A visible status should say both asleep/awake and running/paused so a frozen clock is understandable.\"],\n assess: [\"Check sleep recovery clamping, continued food/joy decay, pause freezing, refused mode changes while paused, retained mode on resume and detached reset. Verify the session-end pause cannot be resumed without reset.\", \"The correct final meter value alone is insufficient if invalid intermediate transitions were accepted.\"],\n inspect: [\"If rest freezes food, inspect whether sleep was incorrectly treated as pause. If resume wakes the creature, inspect coupled assignments. If reset leaves old values, inspect shared nested objects or partial reset logic.\", \"Fresh initial state should be created each time rather than reusing a previously mutated object.\"],\n fix: [\"Separate the mode and pause rules, then replay the complete rest/pause/resume/wake sequence. Recheck awake decay from the previous mission so recovery changes do not replace its rates accidentally.\", \"Use a focused transition table to keep the two independent state dimensions understandable.\"],\n explain: [\"Explain why pause and rest cannot share one meaning and describe what reset affects. Choose the sequence that allows energy recovery while time advances.\", \"Course progress records learning evidence separately, so resetting the fictional habitat is safe for experimentation.\"],\n reward: [\"Save Restful recovery. Your creature can recover energy, pause safely and start again without confusing those operations. Next you will explain successful actions through a bounded history.\", \"Keep the transition table beside your code as a reference for clear controls and feedback.\"],\n },\n questions: {\n learn: { question: \"What is the difference between rest and pause?\", choices: [\"Rest changes mode; pause freezes the simulation\", \"Both delete the habitat\", \"Pause always wakes the creature\"], correctChoice: 0, feedback: \"Rest lets simulated time continue with different energy behaviour, while pause preserves the whole model without advancing it.\" },\n predict: { question: \"Energy starts at 70 asleep and running; after two one-second ticks it is what?\", choices: [\"70\", \"76\", \"68.5\"], correctChoice: 1, feedback: \"Sleeping energy gains three units per second, so two seconds add six before applying the upper bound.\" },\n explain: { question: \"What should reset do to earned course completion?\", choices: [\"Delete it\", \"Replace it with a meter value\", \"Leave it intact while resetting the preview habitat\"], correctChoice: 2, feedback: \"Simulation state and verified learning progress are separate records, allowing safe replay after completion.\" },\n },\n },\n {\n title: \"Explain the care history\", concepts: [\"Event records\", \"Bounded history\", \"Accessible feedback\"],\n goals: [\"Record successful care transitions once using stable event identities.\", \"Make naming, history and feedback understandable without excessive announcements.\"],\n extension: \"Compare a newest-first history with a chronological explanation of the same actions. State which task each ordering helps without changing the stored event contract.\",\n activities: {\n learn: [\"History records what actually happened: one event after each successful feed, play, rest or wake. Give events increasing IDs, keep the newest 20 entries and never record refused actions as successes. A fictional name can personalise the view without changing care rules or creating history events.\", \"Ticks can arrive frequently, so history and live announcements should not be flooded with every numerical change.\"],\n predict: [\"Predict history after feed succeeds, play is refused, rest succeeds and time advances. Then predict its length and IDs after more than twenty successful actions, and what happens when the event counter is exhausted.\", \"Discarding the oldest display entry does not make its identity available for reuse.\"],\n build: [\"Add bounded event recording after successful care transitions and project readable id/text history. Add a labelled naming form with bounded draft input, trim validation, linked errors and aria-invalid. Announce explicit actions and failures through the status message, keeping per-tick need text outside live regions.\", \"Check event-ID availability before committing a care effect so an exhausted counter cannot leave an unrecorded success.\"],\n run: [\"Perform successful and refused care actions and compare each history entry with its resource transition. Rename with valid and invalid fictional text while paused. Read the page with a screen reader while simulated time runs.\", \"A useful announcement conveys a meaningful change; repeated tick chatter can hide the result of the user's actual action.\"],\n assess: [\"Check one event per success, none for refused/tick/name/pause actions, stable IDs, newest-first order, 20-entry bound and counter exhaustion. Check naming validation, retained failed draft, inert text and truthful history summaries.\", \"History is explanatory data, not the authority that awards course completion or replaces protected assessment.\"],\n inspect: [\"If history claims a refused play succeeded, inspect whether recording occurs before guards. If entries repeat after trimming, inspect ID reuse. If naming changes needs, inspect accidental state replacement.\", \"A display label should never become a command or an identity just because the learner typed it.\"],\n fix: [\"Repair event placement, bounds or naming feedback and replay a mixed success/refusal sequence. Verify that errors remain linked to their field and repeated render updates preserve input focus.\", \"Keep the original name until a valid rename commits; the editable draft and accepted name serve different purposes.\"],\n explain: [\"Explain why history contains successful transitions instead of every attempted click or tick. Describe how useful feedback differs from repeatedly announcing the same changing meter.\", \"The person needs a clear account of meaningful actions without losing the ability to read and operate the rest of the page.\"],\n reward: [\"Save Understandable care. The dashboard now explains successful actions and lets learners personalise a fictional creature accessibly. The final mission tests the complete model and interface together.\", \"Keep a history containing both resource and mode changes as a useful final demonstration.\"],\n },\n questions: {\n learn: { question: \"Which action should create a care-history event?\", choices: [\"Every tick\", \"A successful rest transition\", \"A refused play request\"], correctChoice: 1, feedback: \"History records committed care or mode changes, while ticks and refused requests do not claim an action succeeded.\" },\n predict: { question: \"After 21 successful care actions, how many entries should remain?\", choices: [\"21\", \"One\", \"20\"], correctChoice: 2, feedback: \"The history retains the newest twenty entries while keeping its allocation counter increasing so identities are not reused.\" },\n explain: { question: \"Why keep per-tick values outside a live region?\", choices: [\"To avoid overwhelming meaningful user-action feedback\", \"To hide all need values\", \"To prevent keyboard operation\"], correctChoice: 0, feedback: \"Frequent automatic announcements can interrupt reading and obscure the result of deliberate actions or useful errors.\" },\n },\n },\n {\n title: \"A dashboard worth caring for\", concepts: [\"Integrated simulation\", \"Accessible interaction\", \"Capstone\"],\n goals: [\"Deliver a coherent dashboard with care, time, recovery and truthful history.\", \"Verify the exact source across normal, refused, boundary and accessible-use scenarios.\"],\n extension: \"Propose another fictional care action in a separate project save, defining its resource costs, timing, history event and accessible feedback before coding it.\",\n activities: {\n learn: [\"The complete dashboard joins model, controls, time, recovery and explanation. Its theme should make the fictional habitat inviting while labels, bounded rules and useful feedback make it understandable. The final assessment tests the current source, including mistakes and edge cases.\", \"A lively visual scene is useful only when the displayed state and actual action rules continue to agree.\"],\n predict: [\"Plan a demonstration that resumes, feeds, plays, rests, advances time, pauses, renames, resumes, wakes and resets. Predict needs, modes and history entries, including an intentionally refused action.\", \"Use supplied time steps so another person can replay the same demonstration and compare exact results.\"],\n build: [\"Finish the three-file application, review every button permission and label, and keep mode/time/history projections truthful. Add any final theme refinements while preserving readable text, focus and reduced motion preferences.\", \"Do not add automatic browser timers or external services to make the demonstration seem more complete; the host supplies the bounded simulation clock.\"],\n run: [\"Perform the sequence with keyboard and touch, at 320px and enlarged text, in both themes and reduced motion. Check the name form, meter labels, history and announcements with a screen reader. Save/reload the project source and replay.\", \"Keep controls large enough to operate and allow action groups to wrap without changing the meaning of their labels.\"],\n assess: [\"Run final checks for initial values, action guards, resource accounting, deterministic ticks, session expiry, sleep/pause separation, history bounds, naming, nonmutation and reset. Obtain evidence for the saved current source after the whole accessible journey is verified.\", \"Every earlier mission contributes rules to this final application; later polish does not replace their required behaviour.\"],\n inspect: [\"Compare a failure with its earliest divergent state: action eligibility, time rate, mode, history or view. If controls and rules disagree, inspect both the projection and transition rather than changing only the visible disabled state.\", \"A useful reproduction names the starting state and input sequence, not just a screenshot of the eventual mismatch.\"],\n fix: [\"Repair the responsible rule, repeat its boundary case and rerun the full demonstration. Save a named final version and obtain a fresh assessment after the last source change.\", \"Use the earlier exact-threshold and fractional-time examples to protect behaviour during final styling work.\"],\n explain: [\"Explain the difference between rest and pause, why the history is bounded, and how supplied time makes the model testable. Describe one accessibility decision using evidence from your demonstration.\", \"Be precise about the limits of this fictional simulation and avoid suggesting its numeric needs describe real creature care.\"],\n reward: [\"Save Caring dashboard and finish the final assessment. You have built an editable web application that connects interactive controls, deterministic time and understandable feedback. Replay a mission or extend another save while keeping earned completion.\", \"Resetting Moss is a fresh simulation start; your account-bound source and course evidence remain separate.\"],\n },\n questions: {\n learn: { question: \"What should final visual polish preserve?\", choices: [\"Only the background colour\", \"Only the newest control\", \"Truthful state, readable access and all established rules\"], correctChoice: 2, feedback: \"A coherent final interface improves presentation while preserving the working model and accessible interaction paths.\" },\n predict: { question: \"What makes another learner's replay comparable with yours?\", choices: [\"The same initial state and supplied action/time sequence\", \"An unknown browser frame rate\", \"Different random timer delays\"], correctChoice: 0, feedback: \"Explicit starting conditions and deterministic inputs let both runs be compared against the same expected transitions.\" },\n explain: { question: \"Which evidence supports an accessibility claim?\", choices: [\"A decorative icon alone\", \"A verified keyboard, label and announcement journey\", \"A promise to test later\"], correctChoice: 1, feedback: \"Accessibility claims need observed operation through the relevant controls and assistive presentation, not appearance alone.\" },\n },\n },\n]);\n","import {\n MISSION_AUTHORING_CONTRACT_VERSION_V1,\n type LearningModuleVersionV1,\n type MissionArtifactKindV1,\n type MissionAuthoringBundleV1,\n type MissionAuthoringValidationIssueV1,\n type MissionInteractionModeV1,\n type MissionStageKindV1,\n} from \"./contracts.js\";\nimport { validateAssessmentRubric } from \"./rubric-validation.js\";\n\nexport const JUNIOR_CODER_MISSION_STAGE_ORDER_V1 = [\n \"learn\",\n \"predict\",\n \"build\",\n \"run\",\n \"assess\",\n \"inspect\",\n \"fix\",\n \"explain\",\n \"reward\",\n] as const satisfies readonly MissionStageKindV1[];\n\nconst LEARNER_STARTER_KINDS = new Set<MissionArtifactKindV1>([\n \"starter-code\",\n \"starter-assets\",\n \"sample-data\",\n]);\n\nconst LEARNER_FORBIDDEN_KINDS = new Set<MissionArtifactKindV1>([\n \"facilitator-note\",\n \"answer-key\",\n \"protected-test\",\n]);\n\nconst SINGLE_MODE_REQUIRES_ALTERNATIVE = new Set<MissionInteractionModeV1>([\n \"pointer\",\n \"drag\",\n \"audio\",\n \"colour\",\n \"motion\",\n]);\n\nfunction authoringIssue(\n code: MissionAuthoringValidationIssueV1[\"code\"],\n message: string,\n path: string,\n): MissionAuthoringValidationIssueV1 {\n return { code, message, path };\n}\n\nfunction reportDuplicateIds(\n ids: string[],\n path: string,\n): MissionAuthoringValidationIssueV1[] {\n const seen = new Set<string>();\n const issues: MissionAuthoringValidationIssueV1[] = [];\n for (const id of ids) {\n if (seen.has(id)) {\n issues.push(\n authoringIssue(\"duplicate-id\", `Duplicate authored ID ${id}.`, path),\n );\n }\n seen.add(id);\n }\n return issues;\n}\n\n/**\n * Validate learner/facilitator authoring against one immutable catalog module.\n * The complete issue set is returned so authoring tools can fix errors in one pass.\n */\nexport function validateMissionAuthoringBundle(\n bundle: MissionAuthoringBundleV1,\n module: LearningModuleVersionV1,\n): MissionAuthoringValidationIssueV1[] {\n const issues: MissionAuthoringValidationIssueV1[] = [];\n\n if (bundle.version !== MISSION_AUTHORING_CONTRACT_VERSION_V1) {\n issues.push(\n authoringIssue(\n \"bundle-version-mismatch\",\n `Unsupported mission authoring version ${bundle.version}.`,\n \"version\",\n ),\n );\n }\n\n if (bundle.moduleId !== module.id || bundle.moduleVersion !== module.version) {\n issues.push(\n authoringIssue(\n \"module-reference-mismatch\",\n `Bundle ${bundle.moduleId}@${bundle.moduleVersion} does not match ${module.id}@${module.version}.`,\n \"moduleId\",\n ),\n );\n }\n\n if (!module.missions.some((mission) => mission.id === bundle.missionId)) {\n issues.push(\n authoringIssue(\n \"mission-reference-mismatch\",\n `Mission ${bundle.missionId} does not exist in module ${module.id}.`,\n \"missionId\",\n ),\n );\n }\n\n const learner = bundle.learner;\n const facilitator = bundle.facilitator;\n\n if (learner.estimatedMinutes < 15 || learner.estimatedMinutes > 25) {\n issues.push(\n authoringIssue(\n \"invalid-duration\",\n \"A mission must last between 15 and 25 minutes.\",\n \"learner.estimatedMinutes\",\n ),\n );\n }\n\n const stageKinds = learner.stages.map((stage) => stage.kind);\n for (const requiredStage of JUNIOR_CODER_MISSION_STAGE_ORDER_V1) {\n const count = stageKinds.filter((stage) => stage === requiredStage).length;\n if (count === 0) {\n issues.push(\n authoringIssue(\n \"missing-stage\",\n `Mission stage ${requiredStage} is required.`,\n \"learner.stages\",\n ),\n );\n } else if (count > 1) {\n issues.push(\n authoringIssue(\n \"duplicate-stage\",\n `Mission stage ${requiredStage} appears more than once.`,\n \"learner.stages\",\n ),\n );\n }\n }\n if (\n stageKinds.length === JUNIOR_CODER_MISSION_STAGE_ORDER_V1.length\n && stageKinds.some(\n (stage, index) => stage !== JUNIOR_CODER_MISSION_STAGE_ORDER_V1[index],\n )\n ) {\n issues.push(\n authoringIssue(\n \"stage-order\",\n \"Mission stages must follow the canonical learner journey.\",\n \"learner.stages\",\n ),\n );\n }\n\n if (learner.readinessChecks.length === 0) {\n issues.push(\n authoringIssue(\n \"missing-readiness-check\",\n \"At least one unscored readiness check is required.\",\n \"learner.readinessChecks\",\n ),\n );\n }\n if (learner.readinessChecks.some((check) => check.scored !== false)) {\n issues.push(\n authoringIssue(\n \"scored-readiness-check\",\n \"Readiness checks must not affect the deterministic score.\",\n \"learner.readinessChecks\",\n ),\n );\n }\n issues.push(\n ...reportDuplicateIds(\n learner.readinessChecks.map((check) => check.id),\n \"learner.readinessChecks\",\n ),\n );\n\n const learnerArtifactIds = new Set(learner.artifacts.map((artifact) => artifact.id));\n if (!learner.artifacts.some((artifact) => LEARNER_STARTER_KINDS.has(artifact.kind))) {\n issues.push(\n authoringIssue(\n \"missing-starter-artifact\",\n \"At least one learner-safe starter artifact is required.\",\n \"learner.artifacts\",\n ),\n );\n }\n if (\n learner.artifacts.some(\n (artifact) =>\n artifact.audience !== \"learner\"\n || artifact.solutionBearing\n || LEARNER_FORBIDDEN_KINDS.has(artifact.kind),\n )\n ) {\n issues.push(\n authoringIssue(\n \"learner-artifact-leak\",\n \"Learner artifacts cannot contain facilitator or solution-bearing content.\",\n \"learner.artifacts\",\n ),\n );\n }\n if (facilitator.artifacts.some((artifact) => artifact.audience !== \"facilitator\")) {\n issues.push(\n authoringIssue(\n \"facilitator-artifact-leak\",\n \"Facilitator artifacts must remain in the facilitator projection.\",\n \"facilitator.artifacts\",\n ),\n );\n }\n issues.push(\n ...reportDuplicateIds(\n [...learner.artifacts, ...facilitator.artifacts].map((artifact) => artifact.id),\n \"artifacts\",\n ),\n );\n for (const [stageIndex, stage] of learner.stages.entries()) {\n for (const artifactId of stage.artifactIds) {\n if (!learnerArtifactIds.has(artifactId)) {\n issues.push(\n authoringIssue(\n \"unknown-artifact\",\n `Stage references unknown learner artifact ${artifactId}.`,\n `learner.stages[${stageIndex}].artifactIds`,\n ),\n );\n }\n }\n }\n\n if (learner.goals.length === 0) {\n issues.push(\n authoringIssue(\n \"missing-visible-goal\",\n \"At least one visible learner goal is required.\",\n \"learner.goals\",\n ),\n );\n }\n if (facilitator.protectedGoals.length === 0) {\n issues.push(\n authoringIssue(\n \"missing-protected-goal\",\n \"At least one protected facilitator goal is required.\",\n \"facilitator.protectedGoals\",\n ),\n );\n }\n\n const allGoals = [...learner.goals, ...facilitator.protectedGoals];\n const learnerGoalIds = new Set(learner.goals.map((goal) => goal.id));\n const seenGoalIds = new Set<string>();\n for (const goal of allGoals) {\n if (seenGoalIds.has(goal.id)) {\n issues.push(\n authoringIssue(\n \"duplicate-goal-id\",\n `Duplicate goal ID ${goal.id}.`,\n \"goals\",\n ),\n );\n }\n seenGoalIds.add(goal.id);\n }\n if (\n learner.goals.some((goal) => goal.visibility !== \"visible\")\n || facilitator.protectedGoals.some(\n (goal) => goal.visibility !== \"protected\" || goal.completionRequired,\n )\n ) {\n issues.push(\n authoringIssue(\n \"invalid-goal-projection\",\n \"Visible goals belong to learners and protected goals to facilitators.\",\n \"goals\",\n ),\n );\n }\n\n const criterionById = new Map(\n module.assessment.criteria.map((criterion) => [criterion.id, criterion]),\n );\n for (const goal of allGoals) {\n if (goal.criterionIds.length === 0) {\n issues.push(\n authoringIssue(\n \"unknown-criterion\",\n `Goal ${goal.id} must reference a deterministic criterion.`,\n \"goals\",\n ),\n );\n }\n for (const criterionId of goal.criterionIds) {\n const criterion = criterionById.get(criterionId);\n if (!criterion) {\n issues.push(\n authoringIssue(\n \"unknown-criterion\",\n `Goal ${goal.id} references unknown criterion ${criterionId}.`,\n \"goals\",\n ),\n );\n } else if (criterion.visibility !== goal.visibility) {\n issues.push(\n authoringIssue(\n \"criterion-visibility-mismatch\",\n `Goal ${goal.id} cannot expose a ${criterion.visibility} criterion as ${goal.visibility}.`,\n \"goals\",\n ),\n );\n }\n }\n if (goal.completionRequired && goal.aiRequired) {\n issues.push(\n authoringIssue(\n \"ai-dependent-completion\",\n `Completion goal ${goal.id} cannot require AI.`,\n \"goals\",\n ),\n );\n }\n }\n\n for (const rubricIssue of validateAssessmentRubric(module.assessment)) {\n if (\n rubricIssue.code === \"rubric-total\"\n || rubricIssue.code === \"rubric-dimension-total\"\n || rubricIssue.code === \"duplicate-criterion-id\"\n || rubricIssue.code === \"missing-mandatory-safety\"\n ) {\n issues.push(\n authoringIssue(rubricIssue.code, rubricIssue.message, rubricIssue.path),\n );\n }\n }\n\n const alternativeById = new Map(\n learner.accessibilityAlternatives.map((alternative) => [alternative.id, alternative]),\n );\n issues.push(\n ...reportDuplicateIds(\n learner.interactions.map((interaction) => interaction.id),\n \"learner.interactions\",\n ),\n ...reportDuplicateIds(\n learner.accessibilityAlternatives.map((alternative) => alternative.id),\n \"learner.accessibilityAlternatives\",\n ),\n );\n for (const [interactionIndex, interaction] of learner.interactions.entries()) {\n if (\n SINGLE_MODE_REQUIRES_ALTERNATIVE.has(interaction.primaryMode)\n && interaction.alternativeIds.length === 0\n ) {\n issues.push(\n authoringIssue(\n \"inaccessible-interaction\",\n `Interaction ${interaction.id} requires an equivalent alternative.`,\n `learner.interactions[${interactionIndex}]`,\n ),\n );\n }\n for (const alternativeId of interaction.alternativeIds) {\n const alternative = alternativeById.get(alternativeId);\n if (!alternative) {\n issues.push(\n authoringIssue(\n \"unknown-accessibility-alternative\",\n `Interaction ${interaction.id} references unknown alternative ${alternativeId}.`,\n `learner.interactions[${interactionIndex}].alternativeIds`,\n ),\n );\n } else if (\n alternative.equivalentOutcome !== true\n || alternative.modes.length === 0\n || alternative.modes.every((mode) => mode === interaction.primaryMode)\n ) {\n issues.push(\n authoringIssue(\n \"non-equivalent-accessibility-alternative\",\n `Alternative ${alternativeId} must provide an equivalent outcome through another mode.`,\n \"learner.accessibilityAlternatives\",\n ),\n );\n }\n }\n }\n\n if (learner.evidenceRequirements.length === 0) {\n issues.push(\n authoringIssue(\n \"missing-evidence\",\n \"At least one evidence requirement is required.\",\n \"learner.evidenceRequirements\",\n ),\n );\n }\n issues.push(\n ...reportDuplicateIds(\n learner.evidenceRequirements.map((evidence) => evidence.id),\n \"learner.evidenceRequirements\",\n ),\n );\n for (const [evidenceIndex, evidence] of learner.evidenceRequirements.entries()) {\n if (evidence.containsPersonalData !== false) {\n issues.push(\n authoringIssue(\n \"personal-data-evidence\",\n \"Mission evidence cannot request personal data.\",\n `learner.evidenceRequirements[${evidenceIndex}]`,\n ),\n );\n }\n for (const goalId of evidence.goalIds) {\n if (!learnerGoalIds.has(goalId)) {\n issues.push(\n authoringIssue(\n \"unknown-evidence-goal\",\n `Evidence references unknown goal ${goalId}.`,\n `learner.evidenceRequirements[${evidenceIndex}].goalIds`,\n ),\n );\n }\n }\n }\n for (const goal of learner.goals.filter((entry) => entry.completionRequired)) {\n if (\n !learner.evidenceRequirements.some((evidence) => evidence.goalIds.includes(goal.id))\n ) {\n issues.push(\n authoringIssue(\n \"missing-evidence\",\n `Completion goal ${goal.id} requires deterministic evidence.`,\n \"learner.evidenceRequirements\",\n ),\n );\n }\n }\n\n const mandatorySafetyGoals = learner.goals.filter(\n (goal) =>\n goal.completionRequired\n && goal.criterionIds.some((criterionId) => {\n const criterion = criterionById.get(criterionId);\n return criterion?.dimension === \"safety\" && criterion.mandatory;\n }),\n );\n if (\n mandatorySafetyGoals.length === 0\n || !mandatorySafetyGoals.some((goal) =>\n learner.evidenceRequirements.some((evidence) => evidence.goalIds.includes(goal.id)),\n )\n ) {\n issues.push(\n authoringIssue(\n \"missing-safety-evidence\",\n \"A completion-required goal must evidence a mandatory safety criterion.\",\n \"learner.evidenceRequirements\",\n ),\n );\n }\n\n issues.push(\n ...reportDuplicateIds(\n learner.sideAdventures.map((adventure) => adventure.id),\n \"learner.sideAdventures\",\n ),\n );\n if (learner.sideAdventures.length === 0) {\n issues.push(\n authoringIssue(\n \"missing-side-adventure\",\n \"At least one optional side adventure is required.\",\n \"learner.sideAdventures\",\n ),\n );\n }\n if (learner.sideAdventures.some((adventure) => adventure.completionRequired !== false)) {\n issues.push(\n authoringIssue(\n \"mandatory-side-adventure\",\n \"Side adventures must remain optional.\",\n \"learner.sideAdventures\",\n ),\n );\n }\n\n const badgeIds = new Set(module.badges.map((badge) => badge.id));\n issues.push(\n ...reportDuplicateIds(\n learner.rewardBindings.map((reward) => reward.id),\n \"learner.rewardBindings\",\n ),\n );\n for (const [rewardIndex, reward] of learner.rewardBindings.entries()) {\n const rewardInvalid =\n reward.deterministic !== true\n || reward.random !== false\n || reward.tokenConvertible !== false\n || reward.goalIds.length === 0\n || !badgeIds.has(reward.badgeId)\n || reward.goalIds.some((goalId) => !learnerGoalIds.has(goalId));\n if (rewardInvalid) {\n issues.push(\n authoringIssue(\n \"invalid-reward\",\n `Reward ${reward.id} must be deterministic, evidence-bound and non-convertible.`,\n `learner.rewardBindings[${rewardIndex}]`,\n ),\n );\n }\n }\n\n if (learner.functionReference) {\n const functionIds = new Set(\n learner.functionReference.map((entry) => entry.id),\n );\n const invalidFunctionReference =\n functionIds.size !== learner.functionReference.length\n || learner.functionReference.length === 0\n || learner.functionReference.some((entry) => {\n const parameterNames = new Set(\n entry.parameters.map((parameter) => parameter.name),\n );\n return entry.id.trim().length === 0\n || entry.signature.trim().length === 0\n || entry.summary.trim().length === 0\n || entry.effect.trim().length === 0\n || entry.example.trim().length === 0\n || parameterNames.size !== entry.parameters.length\n || entry.parameters.some(\n (parameter) =>\n parameter.name.trim().length === 0\n || parameter.type.trim().length === 0\n || parameter.description.trim().length === 0,\n );\n });\n if (invalidFunctionReference) {\n issues.push(\n authoringIssue(\n \"invalid-function-reference\",\n \"Function references require unique IDs, signatures, parameters, effects and examples.\",\n \"learner.functionReference\",\n ),\n );\n }\n }\n\n if (learner.boundedSuggestion) {\n const suggestion = learner.boundedSuggestion;\n const invalidBoundedSuggestion =\n suggestion.id.trim().length === 0\n || suggestion.source !== \"authored-fallback\"\n || suggestion.intent.trim().length === 0\n || suggestion.constraints.length === 0\n || suggestion.constraints.some((constraint) => constraint.trim().length === 0)\n || !learnerArtifactIds.has(suggestion.permittedArtifactId)\n || suggestion.originalSnippet.trim().length === 0\n || suggestion.replacementSnippet.trim().length === 0\n || suggestion.originalSnippet === suggestion.replacementSnippet\n || suggestion.explanationPrompt.trim().length === 0\n || suggestion.aiOptional !== false\n || suggestion.learnerApprovalRequired !== true\n || suggestion.alternatives.length !== 2\n || suggestion.alternatives[0] !== \"accept\"\n || suggestion.alternatives[1] !== \"reject\";\n if (invalidBoundedSuggestion) {\n issues.push(\n authoringIssue(\n \"invalid-bounded-suggestion\",\n \"A bounded suggestion requires one learner artifact, authored constraints, a visible diff and explicit accept/reject approval.\",\n \"learner.boundedSuggestion\",\n ),\n );\n }\n }\n\n const hardware = bundle.hardware;\n if (hardware) {\n if (\n module.category !== \"robot\"\n || module.hardware.mode !== \"physical-first\"\n || !module.hardware.simulatorAvailable\n ) {\n issues.push(\n authoringIssue(\n \"hardware-module-mismatch\",\n \"Mission hardware disclosure requires a simulator-backed physical robot module.\",\n \"hardware\",\n ),\n );\n }\n\n if (hardware.requirementsVersion !== module.hardware.requirementsVersion) {\n issues.push(\n authoringIssue(\n \"hardware-requirements-version-mismatch\",\n `Hardware disclosure ${hardware.requirementsVersion} does not match catalog requirements ${module.hardware.requirementsVersion}.`,\n \"hardware.requirementsVersion\",\n ),\n );\n }\n\n const catalogHardwareById = new Map(\n module.hardware.items.map((item) => [item.id, item]),\n );\n const completePathIds = new Set(hardware.completePathItemIds);\n const incrementalIds = new Set(hardware.incrementalItemIds);\n const componentIds = new Set(hardware.components.map((component) => component.itemId));\n const catalogIds = new Set(catalogHardwareById.keys());\n const hasDuplicateHardwareIds =\n completePathIds.size !== hardware.completePathItemIds.length\n || incrementalIds.size !== hardware.incrementalItemIds.length\n || componentIds.size !== hardware.components.length;\n const hasUnknownOrMissingItems =\n hasDuplicateHardwareIds\n || completePathIds.size !== catalogIds.size\n || componentIds.size !== catalogIds.size\n || [...catalogIds].some(\n (itemId) => !completePathIds.has(itemId) || !componentIds.has(itemId),\n )\n || [...incrementalIds].some((itemId) => !catalogIds.has(itemId));\n const hasMismatchedComponent = hardware.components.some((component) => {\n const catalogItem = catalogHardwareById.get(component.itemId);\n const expectedScope = incrementalIds.has(component.itemId)\n ? \"incremental\"\n : \"complete-path\";\n return !catalogItem\n || component.quantity !== catalogItem.quantity\n || component.acquisitionScope !== expectedScope;\n });\n if (hasUnknownOrMissingItems || hasMismatchedComponent) {\n issues.push(\n authoringIssue(\n \"hardware-item-mismatch\",\n \"Complete, incremental and per-component hardware disclosures must match the immutable catalog manifest.\",\n \"hardware.components\",\n ),\n );\n }\n\n if (\n hardware.components.some(\n (component) =>\n component.verificationStatus !== \"verified\"\n && component.compatibilityClaimed,\n )\n || (\n module.hardware.verificationStatus !== \"verified\"\n && !module.hardware.publicSaleBlocked\n )\n ) {\n issues.push(\n authoringIssue(\n \"hardware-verification-claim\",\n \"Unverified hardware cannot claim compatibility or unblock public physical sale.\",\n \"hardware.components\",\n ),\n );\n }\n\n const safeguards = hardware.safeguards;\n if (\n safeguards.adultAssemblyRequired !== true\n || safeguards.adultAcknowledgementRequiredForExport !== true\n || safeguards.websiteMayControlHardware !== false\n || safeguards.simulatorCompletionAvailable !== true\n || safeguards.physicalBadgeRequiresAdultSignoff !== true\n || safeguards.adultAssemblySteps.length === 0\n || safeguards.powerRequirements.length === 0\n || safeguards.cableRequirements.length === 0\n || safeguards.softwarePrerequisites.length === 0\n || safeguards.warnings.length === 0\n || hardware.components.some(\n (component) =>\n component.physicalCompletionEligible\n && (\n component.verificationStatus !== \"verified\"\n || module.hardware.verificationStatus !== \"verified\"\n ),\n )\n ) {\n issues.push(\n authoringIssue(\n \"unsafe-physical-export\",\n \"Physical export and completion require adult acknowledgement, verified hardware and a website that never controls hardware.\",\n \"hardware.safeguards\",\n ),\n );\n }\n\n const simulatedBadge = module.badges.find(\n (badge) => badge.id === safeguards.simulatedBadgeId,\n );\n const physicalBadge = module.badges.find(\n (badge) => badge.id === safeguards.physicalBadgeId,\n );\n if (\n simulatedBadge?.evidence === \"adult-physical-signoff\"\n || physicalBadge?.evidence !== \"adult-physical-signoff\"\n ) {\n issues.push(\n authoringIssue(\n \"invalid-hardware-reward\",\n \"Simulated and physical badges must be distinct, and only the physical badge may require adult sign-off.\",\n \"hardware.safeguards\",\n ),\n );\n }\n }\n\n return issues;\n}\n\n/** Fail fast for CI and immutable authoring registration. */\nexport function assertValidMissionAuthoringBundle(\n bundle: MissionAuthoringBundleV1,\n module: LearningModuleVersionV1,\n): void {\n const issues = validateMissionAuthoringBundle(bundle, module);\n if (issues.length === 0) return;\n\n const summary = issues\n .map((entry) => `${entry.code} at ${entry.path}: ${entry.message}`)\n .join(\"\\n\");\n throw new Error(`Invalid mission authoring bundle:\\n${summary}`);\n}\n\n/**\n * Original visual-programming mission for Robot Maze Dash. Learner content\n * contains no protected route, answer key or hidden assessment expectation.\n */\nexport const ROBOT_MAZE_DASH_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.robot-maze-dash\",\n moduleVersion: \"1.1.0\",\n missionId: \"robot-maze-dash-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Meet the move, turn-left and turn-right action blocks and read what each command does.\",\n artifactIds: [\"robot-maze-dash-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict where the robot will stop after it follows the blocks from top to bottom.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Arrange the action blocks so the rescue robot can reach the beacon.\",\n artifactIds: [\"robot-maze-dash-m1-program\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to watch the robot follow your visual program.\",\n artifactIds: [\"robot-maze-dash-m1-program\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic mission checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted block with the first goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Move, add or remove one action block, then run the mission again.\",\n artifactIds: [\"robot-maze-dash-m1-program\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how the order of your blocks changed the robot path.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge when the score and safety check pass.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"robot-maze-dash-m1-read-order\",\n prompt: \"Point to the first action the robot will follow.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"robot-maze-dash-m1-program\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"robot-maze-dash-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"robot-maze-dash-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"robot-maze-dash-m1-starts\",\n statement: \"The visual program is structurally valid and starts.\",\n visibility: \"visible\",\n criterionIds: [\"robot-maze-dash-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"robot-maze-dash-m1-reaches-beacon\",\n statement: \"The robot follows the action order and reaches the rescue beacon.\",\n visibility: \"visible\",\n criterionIds: [\n \"robot-maze-dash-goal-one\",\n \"robot-maze-dash-goal-two\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"robot-maze-dash-m1-safe-preview\",\n statement: \"The robot stays inside the private maze simulator boundary.\",\n visibility: \"visible\",\n criterionIds: [\"robot-maze-dash-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"robot-maze-dash-m1-reorder-blocks\",\n description: \"Change the order of visual action blocks.\",\n primaryMode: \"drag\",\n alternativeIds: [\"robot-maze-dash-m1-button-reorder\"],\n },\n {\n id: \"robot-maze-dash-m1-run-control\",\n description: \"Start the private maze simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"robot-maze-dash-m1-keyboard-run\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"robot-maze-dash-m1-button-reorder\",\n modes: [\"keyboard\", \"pointer\"],\n equivalentOutcome: true,\n description: \"Use labelled Move up and Move down buttons instead of dragging a block.\",\n },\n {\n id: \"robot-maze-dash-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Start the same simulation by pressing Enter or Space on the Run button.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"robot-maze-dash-m1-assessment\",\n goalIds: [\n \"robot-maze-dash-m1-starts\",\n \"robot-maze-dash-m1-reaches-beacon\",\n \"robot-maze-dash-m1-safe-preview\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"robot-maze-dash-m1-explanation\",\n goalIds: [\"robot-maze-dash-m1-reaches-beacon\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"robot-maze-dash-m1-remix\",\n prompt: \"Invent a different safe route and describe which action block must change first.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"robot-maze-dash-m1-badge\",\n badgeId: \"robot-maze-dash-mission-complete\",\n goalIds: [\n \"robot-maze-dash-m1-starts\",\n \"robot-maze-dash-m1-reaches-beacon\",\n \"robot-maze-dash-m1-safe-preview\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"robot-maze-dash-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"robot-maze-dash-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"robot-maze-dash-m1-protected-bounds\",\n statement: \"The interpreter stops safely at walls, bounds and its action limit.\",\n visibility: \"protected\",\n criterionIds: [\n \"robot-maze-dash-edge-one\",\n \"robot-maze-dash-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to point to the first action block before suggesting a change.\",\n \"Use the command reference and visible goal; never reveal the protected route or expected block list.\",\n ],\n },\n};\n\n/**\n * Original first mission for Skywing Sprint. Learner content documents the\n * flight controls without exposing protected numeric targets or source answers.\n */\nexport const SKYWING_SPRINT_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.skywing-sprint\",\n moduleVersion: \"1.1.0\",\n missionId: \"skywing-sprint-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how lift, gravity and gate-gap functions change Skywing's flight.\",\n artifactIds: [\"skywing-sprint-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict whether Skywing will rise or fall after one lift pulse.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the three documented settings in the starter JavaScript.\",\n artifactIds: [\"skywing-sprint-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private flight preview.\",\n artifactIds: [\"skywing-sprint-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic flight checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted setting with the first goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one setting, run again and observe the flight telemetry.\",\n artifactIds: [\"skywing-sprint-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how lift and gravity changed Skywing's vertical speed.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge when the score and safety check pass.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"skywing-sprint-m1-predict-velocity\",\n prompt: \"Point to the setting that changes Skywing's upward push.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"skywing-sprint-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"skywing-sprint-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"skywing-sprint-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"skywing-sprint-m1-starts\",\n statement: \"The JavaScript settings are valid and the private preview starts.\",\n visibility: \"visible\",\n criterionIds: [\"skywing-sprint-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"skywing-sprint-m1-safe-flight\",\n statement: \"Lift and gravity create a controllable flight through the rescue gate.\",\n visibility: \"visible\",\n criterionIds: [\n \"skywing-sprint-goal-one\",\n \"skywing-sprint-goal-two\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"skywing-sprint-m1-private-runtime\",\n statement: \"The game stays inside the private educational preview boundary.\",\n visibility: \"visible\",\n criterionIds: [\"skywing-sprint-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"skywing-sprint-m1-run-control\",\n description: \"Start the private flight simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"skywing-sprint-m1-keyboard-run\"],\n },\n {\n id: \"skywing-sprint-m1-flight-control\",\n description: \"Send a lift pulse while the preview is running.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"skywing-sprint-m1-flight-motion\",\n description: \"Observe Skywing moving through the animated gate preview.\",\n primaryMode: \"motion\",\n alternativeIds: [\"skywing-sprint-m1-reduced-motion\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"skywing-sprint-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same preview.\",\n },\n {\n id: \"skywing-sprint-m1-reduced-motion\",\n modes: [\"text\"],\n equivalentOutcome: true,\n description: \"Use the position, velocity and gate-status text instead of animation.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"skywing-sprint-m1-assessment\",\n goalIds: [\n \"skywing-sprint-m1-starts\",\n \"skywing-sprint-m1-safe-flight\",\n \"skywing-sprint-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"skywing-sprint-m1-explanation\",\n goalIds: [\"skywing-sprint-m1-safe-flight\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"skywing-sprint-m1-remix\",\n prompt: \"Invent a new gate name and choose one setting to make the flight gentler.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"skywing-sprint-m1-badge\",\n badgeId: \"skywing-sprint-mission-complete\",\n goalIds: [\n \"skywing-sprint-m1-starts\",\n \"skywing-sprint-m1-safe-flight\",\n \"skywing-sprint-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"skywing-sprint-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"skywing-sprint-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"skywing-sprint-m1-protected-resilience\",\n statement: \"The runtime clamps unsafe values and terminates bounded simulations.\",\n visibility: \"protected\",\n criterionIds: [\n \"skywing-sprint-edge-one\",\n \"skywing-sprint-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner which direction a positive velocity moves Skywing before suggesting a setting change.\",\n \"Use the visible telemetry and function reference; never reveal protected numeric targets or expected source fragments.\",\n ],\n },\n};\n\n/**\n * Original first mission for Paddle Pulse. Learners tune documented paddle\n * and ball controls without receiving protected collision targets or answers.\n */\nexport const PADDLE_PULSE_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.paddle-pulse\",\n moduleVersion: \"1.1.0\",\n missionId: \"paddle-pulse-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how paddle width, ball speed and bounce angle change an energy-ball rally.\",\n artifactIds: [\"paddle-pulse-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict which direction the energy ball will travel after it reaches the paddle.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the three documented settings in the starter JavaScript.\",\n artifactIds: [\"paddle-pulse-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private energy-court preview.\",\n artifactIds: [\"paddle-pulse-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic rally checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted setting with the first goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one setting, run again and observe the bounce telemetry.\",\n artifactIds: [\"paddle-pulse-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how paddle width and bounce angle changed the energy ball path.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge when the score and safety check pass.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"paddle-pulse-m1-find-angle\",\n prompt: \"Point to the setting that changes the direction of the bounce.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"paddle-pulse-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"paddle-pulse-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"paddle-pulse-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"paddle-pulse-m1-starts\",\n statement: \"The JavaScript settings are valid and the private preview starts.\",\n visibility: \"visible\",\n criterionIds: [\"paddle-pulse-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"paddle-pulse-m1-controlled-bounce\",\n statement: \"The paddle returns the energy ball toward the target wall with a controllable angle.\",\n visibility: \"visible\",\n criterionIds: [\n \"paddle-pulse-goal-one\",\n \"paddle-pulse-goal-two\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"paddle-pulse-m1-private-runtime\",\n statement: \"The game stays inside the private educational preview boundary.\",\n visibility: \"visible\",\n criterionIds: [\"paddle-pulse-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"paddle-pulse-m1-run-control\",\n description: \"Start the private energy-court simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"paddle-pulse-m1-keyboard-run\"],\n },\n {\n id: \"paddle-pulse-m1-paddle-control\",\n description: \"Move the paddle left or right during practice.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"paddle-pulse-m1-ball-motion\",\n description: \"Observe the energy ball moving and bouncing across the court.\",\n primaryMode: \"motion\",\n alternativeIds: [\"paddle-pulse-m1-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"paddle-pulse-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same preview.\",\n },\n {\n id: \"paddle-pulse-m1-telemetry\",\n modes: [\"text\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Use position, direction and target-status text instead of ball animation.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"paddle-pulse-m1-assessment\",\n goalIds: [\n \"paddle-pulse-m1-starts\",\n \"paddle-pulse-m1-controlled-bounce\",\n \"paddle-pulse-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"paddle-pulse-m1-explanation\",\n goalIds: [\"paddle-pulse-m1-controlled-bounce\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"paddle-pulse-m1-remix\",\n prompt: \"Invent an original energy power-up and describe one bounded setting it would change.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"paddle-pulse-m1-badge\",\n badgeId: \"paddle-pulse-mission-complete\",\n goalIds: [\n \"paddle-pulse-m1-starts\",\n \"paddle-pulse-m1-controlled-bounce\",\n \"paddle-pulse-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"paddle-pulse-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"paddle-pulse-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"paddle-pulse-m1-protected-resilience\",\n statement: \"The runtime clamps unsafe settings and terminates bounded collision simulations.\",\n visibility: \"protected\",\n criterionIds: [\n \"paddle-pulse-edge-one\",\n \"paddle-pulse-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner which setting changes direction before suggesting a code edit.\",\n \"Use visible telemetry and the function reference; never reveal protected numeric targets or expected source fragments.\",\n ],\n },\n};\n\n/**\n * Original first mission for Pixel Trail Challenge. Learners use documented,\n * bounded Python host functions without receiving protected coordinates,\n * expected source fragments or list/collision edge answers.\n */\nexport const PIXEL_TRAIL_CHALLENGE_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.pixel-trail-challenge\",\n moduleVersion: \"1.1.0\",\n missionId: \"pixel-trail-challenge-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read what set_direction(), set_trail_limit() and place_energy_orb() do in the private Python preview.\",\n artifactIds: [\"pixel-trail-challenge-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict the next grid square and how the trail list will change after one move.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the three documented Python calls so the pixel follows a safe trail toward the energy orb.\",\n artifactIds: [\"pixel-trail-challenge-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private grid preview.\",\n artifactIds: [\"pixel-trail-challenge-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic trail checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted Python line with the first goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one direction, trail or orb setting, run again and inspect the position and list-length telemetry.\",\n artifactIds: [\"pixel-trail-challenge-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how the direction changed the position and why the trail list kept only recent squares.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge when the score and private-runtime safety check pass.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"pixel-trail-challenge-m1-find-direction\",\n prompt: \"Point to the Python call that chooses the pixel's next direction.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"pixel-trail-challenge-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"pixel-trail-challenge-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"pixel-trail-challenge-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"pixel-trail-challenge-m1-starts\",\n statement: \"The Python settings are valid and the private grid preview starts.\",\n visibility: \"visible\",\n criterionIds: [\"pixel-trail-challenge-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"pixel-trail-challenge-m1-safe-trail\",\n statement: \"The pixel moves in the chosen direction, keeps a bounded trail list and reaches the energy orb.\",\n visibility: \"visible\",\n criterionIds: [\n \"pixel-trail-challenge-goal-one\",\n \"pixel-trail-challenge-goal-two\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"pixel-trail-challenge-m1-private-runtime\",\n statement: \"The program stays inside the private Python worker and host-provided grid API.\",\n visibility: \"visible\",\n criterionIds: [\"pixel-trail-challenge-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"pixel-trail-challenge-m1-run-control\",\n description: \"Start the private Python grid simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"pixel-trail-challenge-m1-keyboard-run\"],\n },\n {\n id: \"pixel-trail-challenge-m1-direction-control\",\n description: \"Change the active movement direction with labelled arrow controls or arrow keys.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"pixel-trail-challenge-m1-trail-motion\",\n description: \"Observe the pixel, recent trail squares and energy orb on the grid.\",\n primaryMode: \"motion\",\n alternativeIds: [\"pixel-trail-challenge-m1-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"pixel-trail-challenge-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same preview.\",\n },\n {\n id: \"pixel-trail-challenge-m1-telemetry\",\n modes: [\"text\", \"shape\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read row, column, direction, trail length and orb status without animation or colour dependence.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"pixel-trail-challenge-m1-assessment\",\n goalIds: [\n \"pixel-trail-challenge-m1-starts\",\n \"pixel-trail-challenge-m1-safe-trail\",\n \"pixel-trail-challenge-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"pixel-trail-challenge-m1-explanation\",\n goalIds: [\"pixel-trail-challenge-m1-safe-trail\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"pixel-trail-challenge-m1-remix\",\n prompt: \"Invent an original energy-orb symbol and describe a new safe grid rule for collecting it.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"pixel-trail-challenge-m1-badge\",\n badgeId: \"pixel-trail-challenge-mission-complete\",\n goalIds: [\n \"pixel-trail-challenge-m1-starts\",\n \"pixel-trail-challenge-m1-safe-trail\",\n \"pixel-trail-challenge-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"pixel-trail-challenge-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"pixel-trail-challenge-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"pixel-trail-challenge-m1-protected-resilience\",\n statement: \"The worker rejects invalid directions, clamps trail capacity and terminates bounded grid simulations before list or collision abuse.\",\n visibility: \"protected\",\n criterionIds: [\n \"pixel-trail-challenge-edge-one\",\n \"pixel-trail-challenge-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to predict the next row and column before suggesting a Python edit.\",\n \"Use the function reference and visible telemetry; never reveal protected coordinates, numeric targets or expected source fragments.\",\n ],\n },\n};\n\n/**\n * Original first mission for Star Defender Squadron. Learners launch bounded\n * JavaScript entities, patterns, health and rescue projectiles while protected\n * pass targets and runtime edge cases remain facilitator-only.\n */\nexport const STAR_DEFENDER_SQUADRON_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.star-defender-squadron\",\n moduleVersion: \"1.1.0\",\n missionId: \"star-defender-squadron-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read what createSquadron(), setRescueWave(), setShieldHealth() and launchRescueBeam() do in the private JavaScript preview.\",\n artifactIds: [\"star-defender-squadron-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict where the squadron and rescue beam will travel, and which health value will change after the wave.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the four documented JavaScript calls so the original squadron launches a safe rescue wave.\",\n artifactIds: [\"star-defender-squadron-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private Star Defender preview.\",\n artifactIds: [\"star-defender-squadron-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic squadron checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted JavaScript line with the first mission goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one squadron, wave, shield or beam setting, then rerun and inspect the entity and health telemetry.\",\n artifactIds: [\"star-defender-squadron-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how the wave pattern moved the entities and how shields protected the rescue mission.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge when the score and private-runtime safety check pass.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"star-defender-squadron-m1-find-wave\",\n prompt: \"Point to the JavaScript call that chooses the rescue-wave pattern.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"star-defender-squadron-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"star-defender-squadron-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"star-defender-squadron-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"star-defender-squadron-m1-starts\",\n statement: \"The JavaScript settings are valid and the private squadron preview starts.\",\n visibility: \"visible\",\n criterionIds: [\"star-defender-squadron-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"star-defender-squadron-m1-rescue-wave\",\n statement: \"The original squadron follows the chosen pattern, keeps safe shield health and launches a rescue beam.\",\n visibility: \"visible\",\n criterionIds: [\n \"star-defender-squadron-goal-one\",\n \"star-defender-squadron-goal-two\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"star-defender-squadron-m1-private-runtime\",\n statement: \"The program stays inside the private JavaScript worker and host-provided space-rescue API.\",\n visibility: \"visible\",\n criterionIds: [\"star-defender-squadron-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"star-defender-squadron-m1-run-control\",\n description: \"Start the private JavaScript squadron simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"star-defender-squadron-m1-keyboard-run\"],\n },\n {\n id: \"star-defender-squadron-m1-code-control\",\n description: \"Edit the documented squadron, wave, shield and beam calls.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"star-defender-squadron-m1-wave-motion\",\n description: \"Observe squadron entities, wave paths, shields and the rescue beam.\",\n primaryMode: \"motion\",\n alternativeIds: [\"star-defender-squadron-m1-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"star-defender-squadron-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same preview.\",\n },\n {\n id: \"star-defender-squadron-m1-telemetry\",\n modes: [\"text\", \"shape\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read entity count, pattern, shield health, beam state and rescue result without animation or colour dependence.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"star-defender-squadron-m1-assessment\",\n goalIds: [\n \"star-defender-squadron-m1-starts\",\n \"star-defender-squadron-m1-rescue-wave\",\n \"star-defender-squadron-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"star-defender-squadron-m1-explanation\",\n goalIds: [\"star-defender-squadron-m1-rescue-wave\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"star-defender-squadron-m1-remix\",\n prompt: \"Invent an original rescue-squadron emblem and describe a new safe wave pattern for a later level.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"star-defender-squadron-m1-badge\",\n badgeId: \"star-defender-squadron-mission-complete\",\n goalIds: [\n \"star-defender-squadron-m1-starts\",\n \"star-defender-squadron-m1-rescue-wave\",\n \"star-defender-squadron-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"star-defender-squadron-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"star-defender-squadron-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"star-defender-squadron-m1-protected-resilience\",\n statement: \"The worker rejects invalid entity, pattern, health and projectile settings and terminates bounded wave simulations.\",\n visibility: \"protected\",\n criterionIds: [\n \"star-defender-squadron-edge-one\",\n \"star-defender-squadron-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to predict the squadron path and shield change before suggesting a JavaScript edit.\",\n \"Use the function reference and visible telemetry; never reveal protected numeric targets, pattern answers or expected source fragments.\",\n ],\n },\n};\n\n/**\n * First Beacon Bot robotics mission. The learner completes a bounded simulator\n * route; every physical item remains unverified, public-sale blocked and\n * ineligible for physical completion until an adult bench-test authority says\n * otherwise.\n */\nexport const BEACON_BOT_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.beacon-bot\",\n moduleVersion: \"1.1.0\",\n missionId: \"beacon-bot-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read what setVisibleSignal(), waitMs(), repeatSignal() and readIrReceiver() do in the private Beacon Bot simulator.\",\n artifactIds: [\"beacon-bot-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict the visible signal order, elapsed time and simulated IR reading before the sequence runs.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the four documented C++-style calls to create one bounded rescue signal.\",\n artifactIds: [\"beacon-bot-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private Beacon Bot simulator.\",\n artifactIds: [\"beacon-bot-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic beacon checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted C++-style line with the first signal goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one signal, wait, repeat or simulated IR setting, then rerun and inspect the text telemetry.\",\n artifactIds: [\"beacon-bot-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how the function calls created a timed signal and how the simulated receiver changed the result.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the simulated badge when the score and private-runtime safety check pass; physical completion remains adult-only.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"beacon-bot-m1-find-wait\",\n prompt: \"Point to the documented call that controls how long a visible signal stays on.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"beacon-bot-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"beacon-bot-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"beacon-bot-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"beacon-bot-m1-starts\",\n statement: \"The documented C++-style settings are valid and the private simulator starts.\",\n visibility: \"visible\",\n criterionIds: [\"beacon-bot-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"beacon-bot-m1-signal-sequence\",\n statement: \"The beacon produces a bounded timed pattern and reports one simulated IR receiver state.\",\n visibility: \"visible\",\n criterionIds: [\"beacon-bot-goal-one\", \"beacon-bot-goal-two\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"beacon-bot-m1-private-runtime\",\n statement: \"The program stays inside the private simulator and never accesses physical hardware, the network or browser storage.\",\n visibility: \"visible\",\n criterionIds: [\"beacon-bot-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"beacon-bot-m1-run-control\",\n description: \"Start the private Beacon Bot signal simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"beacon-bot-m1-keyboard-run\"],\n },\n {\n id: \"beacon-bot-m1-code-control\",\n description: \"Edit the documented signal, timing, repeat and receiver calls.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"beacon-bot-m1-signal-colour\",\n description: \"Observe the visible rescue signal without relying on colour alone.\",\n primaryMode: \"colour\",\n alternativeIds: [\"beacon-bot-m1-signal-telemetry\"],\n },\n {\n id: \"beacon-bot-m1-signal-motion\",\n description: \"Observe the bounded signal sequence and receiver state changes.\",\n primaryMode: \"motion\",\n alternativeIds: [\"beacon-bot-m1-signal-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"beacon-bot-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same simulator.\",\n },\n {\n id: \"beacon-bot-m1-signal-telemetry\",\n modes: [\"text\", \"shape\", \"symbol\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read the signal name, step count, elapsed milliseconds and receiver state without colour or animation.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"beacon-bot-m1-assessment\",\n goalIds: [\n \"beacon-bot-m1-starts\",\n \"beacon-bot-m1-signal-sequence\",\n \"beacon-bot-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"beacon-bot-m1-explanation\",\n goalIds: [\"beacon-bot-m1-signal-sequence\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"beacon-bot-m1-remix\",\n prompt: \"Invent an original rescue-signal name and describe a text or shape cue that makes it understandable without colour.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"beacon-bot-m1-simulated-badge\",\n badgeId: \"beacon-bot-mission-complete\",\n goalIds: [\n \"beacon-bot-m1-starts\",\n \"beacon-bot-m1-signal-sequence\",\n \"beacon-bot-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"beacon-bot-function-visible-signal\",\n signature: \"setVisibleSignal(colour)\",\n summary: \"Chooses the named visible signal used by the next bounded step.\",\n parameters: [\n {\n name: \"colour\",\n type: \"string\",\n description: \"Use red, amber or green.\",\n },\n ],\n effect: \"Updates the simulator's labelled light and equivalent shape cue without accessing a physical LED.\",\n example: \"setVisibleSignal(\\\"green\\\");\",\n },\n {\n id: \"beacon-bot-function-wait\",\n signature: \"waitMs(duration)\",\n summary: \"Adds one safe wait to the simulated signal timeline.\",\n parameters: [\n {\n name: \"duration\",\n type: \"whole number\",\n description: \"A bounded number of milliseconds from 100 to 1000.\",\n },\n ],\n effect: \"Advances simulated elapsed time; it never blocks the website or controls hardware.\",\n example: \"waitMs(250);\",\n },\n {\n id: \"beacon-bot-function-repeat\",\n signature: \"repeatSignal(count)\",\n summary: \"Repeats the current visible signal a safe number of times.\",\n parameters: [\n {\n name: \"count\",\n type: \"whole number\",\n description: \"A bounded repeat count from 1 to 4.\",\n },\n ],\n effect: \"Adds a fixed number of labelled signal steps to the private simulator timeline.\",\n example: \"repeatSignal(3);\",\n },\n {\n id: \"beacon-bot-function-ir-receiver\",\n signature: \"readIrReceiver()\",\n summary: \"Reads the simulator's fictional infrared receiver state.\",\n parameters: [],\n effect: \"Returns detected or clear from simulator state only; it cannot access a real sensor.\",\n example: \"const receiverState = readIrReceiver();\",\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"beacon-bot-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"beacon-bot-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"beacon-bot-m1-adult-hardware-guide\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"beacon-bot-m1-protected-resilience\",\n statement: \"The simulator rejects unsupported signals, excessive waits or repeats and any hardware, network or storage request.\",\n visibility: \"protected\",\n criterionIds: [\"beacon-bot-edge-one\", \"beacon-bot-edge-two\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to predict the labelled signal timeline before suggesting one bounded change.\",\n \"Use the function reference and visible telemetry; never provide wiring or physical power advice to a learner.\",\n \"Physical export stays unavailable until an adult acknowledges the exact manifest and every component has verified bench-test evidence.\",\n ],\n },\n hardware: {\n requirementsVersion: \"1.0.0\",\n hardwareIncluded: false,\n completePathItemIds: [\n \"pico-2-w\",\n \"breadboard\",\n \"usb-data-cable\",\n \"jumper-wires\",\n \"led-pack\",\n \"led-resistors\",\n \"ir-pair\",\n ],\n incrementalItemIds: [\"led-pack\", \"led-resistors\", \"ir-pair\"],\n components: [\n {\n itemId: \"pico-2-w\",\n quantity: 1,\n acquisitionScope: \"complete-path\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"breadboard\",\n quantity: 1,\n acquisitionScope: \"complete-path\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"usb-data-cable\",\n quantity: 1,\n acquisitionScope: \"complete-path\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"jumper-wires\",\n quantity: 12,\n acquisitionScope: \"complete-path\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"led-pack\",\n quantity: 3,\n acquisitionScope: \"incremental\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"led-resistors\",\n quantity: 3,\n acquisitionScope: \"incremental\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"ir-pair\",\n quantity: 1,\n acquisitionScope: \"incremental\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n ],\n safeguards: {\n adultAssemblyRequired: true,\n adultAcknowledgementRequiredForExport: true,\n websiteMayControlHardware: false,\n simulatorCompletionAvailable: true,\n simulatedBadgeId: \"beacon-bot-mission-complete\",\n physicalBadgeId: \"beacon-bot-physical-builder\",\n physicalBadgeRequiresAdultSignoff: true,\n adultAssemblySteps: [\n \"Confirm every exact component identity against the requirements manifest.\",\n \"Assemble and inspect the disconnected breadboard circuit before learner use.\",\n \"Run known-good recovery firmware and complete the adult bench-test record.\",\n ],\n powerRequirements: [\n \"Use Pico USB power only for the published Beacon Bot reference circuit.\",\n \"Disconnect USB power before changing any wiring.\",\n ],\n cableRequirements: [\n \"One known data-capable USB cable compatible with the Pico 2 W.\",\n \"Insulated male-to-male breadboard jumper wires matching the manifest quantity.\",\n ],\n softwarePrerequisites: [\n \"Supported Pico SDK toolchain on Raspberry Pi OS or a documented desktop environment.\",\n \"Known-good Beacon Bot recovery firmware prepared by an adult.\",\n ],\n warnings: [\n \"Hardware is not included with the module.\",\n \"No listed component currently claims compatibility or physical-completion eligibility.\",\n \"The simulator and simulated badge remain available without physical equipment.\",\n ],\n unrelatedHardwareNotRequired: [\n \"Camera Module 3\",\n \"motor driver or motors\",\n \"servo\",\n ],\n },\n },\n};\n\n/**\n * First Servo Creature robotics mission. The learner creates a bounded pose,\n * mood and interaction sequence in the simulator. Physical servo power and\n * movement remain unavailable until the exact reference build is bench tested.\n */\nexport const SERVO_CREATURE_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.servo-creature\",\n moduleVersion: \"1.1.0\",\n missionId: \"servo-creature-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read what setServoAngle(), waitMs(), repeatMovement(), setCreatureMood() and readTouchSensor() do in the private Servo Creature simulator.\",\n artifactIds: [\"servo-creature-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict the creature's labelled angle, mood, repeat count and simulated touch response before the sequence runs.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the five documented C++-style calls to create one bounded creature movement sequence.\",\n artifactIds: [\"servo-creature-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private Servo Creature simulator.\",\n artifactIds: [\"servo-creature-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic pose, mood and interaction checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted C++-style line with the first movement goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one angle, wait, repeat, mood or simulated touch call, then rerun and inspect the text telemetry.\",\n artifactIds: [\"servo-creature-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how the bounded calls created a safe movement and how the simulated interaction changed the creature's response.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the simulated badge when the score and private-runtime safety check pass; physical completion remains adult-only.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"servo-creature-m1-find-angle-limit\",\n prompt: \"Find the documented safe minimum and maximum angle before changing the creature's pose.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"servo-creature-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"servo-creature-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"servo-creature-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"servo-creature-m1-starts\",\n statement: \"The documented C++-style settings are valid and the private simulator starts.\",\n visibility: \"visible\",\n criterionIds: [\"servo-creature-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"servo-creature-m1-movement-sequence\",\n statement: \"The creature completes a bounded angle, timing, mood and interaction sequence.\",\n visibility: \"visible\",\n criterionIds: [\"servo-creature-goal-one\", \"servo-creature-goal-two\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"servo-creature-m1-private-runtime\",\n statement: \"The program stays inside the private simulator and never accesses physical hardware, the network or browser storage.\",\n visibility: \"visible\",\n criterionIds: [\"servo-creature-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"servo-creature-m1-run-control\",\n description: \"Start the private Servo Creature movement simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"servo-creature-m1-keyboard-run\"],\n },\n {\n id: \"servo-creature-m1-code-control\",\n description: \"Edit the documented angle, timing, repeat, mood and interaction calls.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"servo-creature-m1-pose-motion\",\n description: \"Observe the bounded creature pose and movement sequence.\",\n primaryMode: \"motion\",\n alternativeIds: [\"servo-creature-m1-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"servo-creature-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same simulator.\",\n },\n {\n id: \"servo-creature-m1-telemetry\",\n modes: [\"text\", \"shape\", \"symbol\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read the angle, mood, repeat count, elapsed milliseconds and touch state without animation.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"servo-creature-m1-assessment\",\n goalIds: [\n \"servo-creature-m1-starts\",\n \"servo-creature-m1-movement-sequence\",\n \"servo-creature-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"servo-creature-m1-explanation\",\n goalIds: [\"servo-creature-m1-movement-sequence\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"servo-creature-m1-remix\",\n prompt: \"Invent an original creature mood and describe a text or symbol cue that makes its pose understandable without movement.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"servo-creature-m1-simulated-badge\",\n badgeId: \"servo-creature-mission-complete\",\n goalIds: [\n \"servo-creature-m1-starts\",\n \"servo-creature-m1-movement-sequence\",\n \"servo-creature-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"servo-creature-function-angle\",\n signature: \"setServoAngle(degrees)\",\n summary: \"Chooses one safe labelled creature pose in the private simulator.\",\n parameters: [\n {\n name: \"degrees\",\n type: \"whole number\",\n description: \"A bounded angle from 30 to 150 degrees.\",\n },\n ],\n effect: \"Updates the simulator's labelled angle and pose cue without generating PWM or accessing a physical servo.\",\n example: \"setServoAngle(90);\",\n },\n {\n id: \"servo-creature-function-wait\",\n signature: \"waitMs(duration)\",\n summary: \"Adds one safe wait to the simulated movement timeline.\",\n parameters: [\n {\n name: \"duration\",\n type: \"whole number\",\n description: \"A bounded number of milliseconds from 100 to 1000.\",\n },\n ],\n effect: \"Advances simulated elapsed time; it never blocks the website or holds a physical servo under load.\",\n example: \"waitMs(300);\",\n },\n {\n id: \"servo-creature-function-repeat\",\n signature: \"repeatMovement(count)\",\n summary: \"Repeats the current simulated pose a safe number of times.\",\n parameters: [\n {\n name: \"count\",\n type: \"whole number\",\n description: \"A bounded repeat count from 1 to 4.\",\n },\n ],\n effect: \"Adds a fixed number of labelled pose steps to the private simulator timeline.\",\n example: \"repeatMovement(3);\",\n },\n {\n id: \"servo-creature-function-mood\",\n signature: \"setCreatureMood(mood)\",\n summary: \"Chooses the creature's labelled expression for the simulated pose.\",\n parameters: [\n {\n name: \"mood\",\n type: \"string\",\n description: \"Use calm, curious or happy.\",\n },\n ],\n effect: \"Updates the simulator's text and symbol mood cue without moving physical parts.\",\n example: \"setCreatureMood(\\\"curious\\\");\",\n },\n {\n id: \"servo-creature-function-touch\",\n signature: \"readTouchSensor()\",\n summary: \"Reads the simulator's fictional touch state for one interaction response.\",\n parameters: [],\n effect: \"Returns touched or clear from simulator state only; it cannot access a physical sensor.\",\n example: \"const touchState = readTouchSensor();\",\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"servo-creature-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"servo-creature-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"servo-creature-m1-adult-hardware-guide\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"servo-creature-m1-protected-resilience\",\n statement: \"The simulator rejects unsupported moods, out-of-range angles, excessive waits or repeats and any physical-hardware request.\",\n visibility: \"protected\",\n criterionIds: [\"servo-creature-edge-one\", \"servo-creature-edge-two\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to predict the labelled pose timeline before suggesting one bounded change.\",\n \"Use the function reference and visible telemetry; never provide servo wiring, power or movement advice to a learner.\",\n \"Physical export stays unavailable until an adult acknowledges the exact manifest and every servo power component has verified bench-test evidence.\",\n ],\n },\n hardware: {\n requirementsVersion: \"1.0.0\",\n hardwareIncluded: false,\n completePathItemIds: [\n \"pico-2-w\",\n \"breadboard\",\n \"usb-data-cable\",\n \"jumper-wires\",\n \"micro-servo\",\n \"servo-power\",\n ],\n incrementalItemIds: [\"micro-servo\", \"servo-power\"],\n components: [\n {\n itemId: \"pico-2-w\",\n quantity: 1,\n acquisitionScope: \"complete-path\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"breadboard\",\n quantity: 1,\n acquisitionScope: \"complete-path\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"usb-data-cable\",\n quantity: 1,\n acquisitionScope: \"complete-path\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"jumper-wires\",\n quantity: 12,\n acquisitionScope: \"complete-path\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"micro-servo\",\n quantity: 1,\n acquisitionScope: \"incremental\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n {\n itemId: \"servo-power\",\n quantity: 1,\n acquisitionScope: \"incremental\",\n verificationStatus: \"pending-bench-test\",\n compatibilityClaimed: false,\n physicalCompletionEligible: false,\n },\n ],\n safeguards: {\n adultAssemblyRequired: true,\n adultAcknowledgementRequiredForExport: true,\n websiteMayControlHardware: false,\n simulatorCompletionAvailable: true,\n simulatedBadgeId: \"servo-creature-mission-complete\",\n physicalBadgeId: \"servo-creature-physical-builder\",\n physicalBadgeRequiresAdultSignoff: true,\n adultAssemblySteps: [\n \"Confirm the exact servo, external supply and connector identities against the requirements manifest.\",\n \"Assemble and inspect the disconnected signal and common-ground wiring before learner use.\",\n \"Secure the creature linkage, lift or restrain moving parts and complete the adult bench-test record.\",\n ],\n powerRequirements: [\n \"Use an external regulated servo supply sized for the verified servo; do not power the servo from a Pico GPIO pin.\",\n \"Connect one common signal ground between the verified servo supply and Pico only as shown in the adult guide.\",\n \"Disconnect every power source before changing wiring or creature linkages.\",\n ],\n cableRequirements: [\n \"One known data-capable USB cable compatible with the Pico 2 W.\",\n \"Insulated jumper leads and a verified servo connector arrangement documented by the adult guide.\",\n ],\n softwarePrerequisites: [\n \"Supported Pico SDK toolchain on Raspberry Pi OS or a documented desktop environment.\",\n \"Known-good Servo Creature recovery firmware with adult-owned neutral-pose and stop behaviour.\",\n ],\n warnings: [\n \"Hardware is not included with the module.\",\n \"No listed servo or power arrangement currently claims compatibility or physical-completion eligibility.\",\n \"Pinch points, stalled servos and unsuitable power supplies can cause heat or movement; adult assembly and testing are mandatory.\",\n \"The simulator and simulated badge remain available without physical equipment.\",\n ],\n unrelatedHardwareNotRequired: [\n \"Camera Module 3 or Raspberry Pi Zero 2 W\",\n \"motor driver, motors or rover chassis\",\n \"physical touch or IR sensor\",\n \"LED or infrared beacon parts\",\n ],\n },\n },\n};\n\n/**\n * First Dance Rover robotics mission. Learners choreograph a bounded rover\n * sequence in the private simulator. Motor power, firmware export and physical\n * movement remain unavailable until the exact reference build is bench tested.\n */\nexport const DANCE_ROVER_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.dance-rover\",\n moduleVersion: \"1.1.0\",\n missionId: \"dance-rover-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read what driveRover(), turnRover(), waitMs(), repeatDance() and emergencyStop() do in the private Dance Rover simulator.\",\n artifactIds: [\"dance-rover-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict the rover's labelled direction, speed, turn, repeat count and final stopped state before the dance runs.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the five documented C++-style calls to create one bounded rover dance with an emergency stop.\",\n artifactIds: [\"dance-rover-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private Dance Rover simulator.\",\n artifactIds: [\"dance-rover-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic direction, speed, sequence and stop checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted C++-style line with the first dance goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one bounded direction, speed, wait, repeat or stop call, then rerun and inspect the text telemetry.\",\n artifactIds: [\"dance-rover-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how reusable movement calls created the choreography and why every safe dance ends stopped.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the simulated badge when the score and fail-safe stop pass; physical completion remains adult-only.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"dance-rover-m1-find-stop\",\n prompt: \"Find the emergencyStop() call and explain why it must finish every physical movement sequence.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"dance-rover-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"dance-rover-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"dance-rover-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"dance-rover-m1-starts\",\n statement: \"The documented C++-style settings are valid and the private simulator starts.\",\n visibility: \"visible\",\n criterionIds: [\"dance-rover-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"dance-rover-m1-choreography\",\n statement: \"The rover completes a bounded direction, speed, turn and repeat sequence before stopping.\",\n visibility: \"visible\",\n criterionIds: [\"dance-rover-goal-one\", \"dance-rover-goal-two\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"dance-rover-m1-private-runtime\",\n statement: \"The program stays inside the private simulator and never accesses physical motors, the network or browser storage.\",\n visibility: \"visible\",\n criterionIds: [\"dance-rover-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"dance-rover-m1-run-control\",\n description: \"Start the private Dance Rover choreography simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"dance-rover-m1-keyboard-run\"],\n },\n {\n id: \"dance-rover-m1-code-control\",\n description: \"Edit the documented direction, speed, wait, repeat and stop calls.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"dance-rover-m1-motion-preview\",\n description: \"Observe the bounded rover route and stopped state.\",\n primaryMode: \"motion\",\n alternativeIds: [\"dance-rover-m1-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"dance-rover-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same simulator.\",\n },\n {\n id: \"dance-rover-m1-telemetry\",\n modes: [\"text\", \"shape\", \"symbol\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read direction, speed, turn, repeat count, elapsed milliseconds and stopped state without animation.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"dance-rover-m1-assessment\",\n goalIds: [\n \"dance-rover-m1-starts\",\n \"dance-rover-m1-choreography\",\n \"dance-rover-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"dance-rover-m1-explanation\",\n goalIds: [\"dance-rover-m1-choreography\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"dance-rover-m1-remix\",\n prompt: \"Invent an original rover dance and add a text or symbol route cue that makes it understandable without motion.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"dance-rover-m1-simulated-badge\",\n badgeId: \"dance-rover-mission-complete\",\n goalIds: [\n \"dance-rover-m1-starts\",\n \"dance-rover-m1-choreography\",\n \"dance-rover-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"dance-rover-function-drive\",\n signature: \"driveRover(direction, speed)\",\n summary: \"Adds one straight movement to the private simulator route.\",\n parameters: [\n {\n name: \"direction\",\n type: \"string\",\n description: \"Use forward or backward.\",\n },\n {\n name: \"speed\",\n type: \"whole number\",\n description: \"A bounded simulated speed from 0 to 60 percent.\",\n },\n ],\n effect: \"Updates the simulator's labelled route without generating motor PWM or accessing a physical driver.\",\n example: \"driveRover(\\\"forward\\\", 40);\",\n },\n {\n id: \"dance-rover-function-turn\",\n signature: \"turnRover(direction, speed)\",\n summary: \"Adds one left or right turn to the private simulator route.\",\n parameters: [\n {\n name: \"direction\",\n type: \"string\",\n description: \"Use left or right.\",\n },\n {\n name: \"speed\",\n type: \"whole number\",\n description: \"A bounded simulated turn speed from 0 to 60 percent.\",\n },\n ],\n effect: \"Updates labelled simulator direction without energising motors or a driver.\",\n example: \"turnRover(\\\"left\\\", 30);\",\n },\n {\n id: \"dance-rover-function-wait\",\n signature: \"waitMs(duration)\",\n summary: \"Adds one bounded wait to the simulated dance timeline.\",\n parameters: [\n {\n name: \"duration\",\n type: \"whole number\",\n description: \"A bounded number of milliseconds from 100 to 1000.\",\n },\n ],\n effect: \"Advances simulated elapsed time; it never blocks the website or holds physical motors under load.\",\n example: \"waitMs(300);\",\n },\n {\n id: \"dance-rover-function-repeat\",\n signature: \"repeatDance(count)\",\n summary: \"Repeats the current simulated dance a safe number of times.\",\n parameters: [\n {\n name: \"count\",\n type: \"whole number\",\n description: \"A bounded repeat count from 1 to 4.\",\n },\n ],\n effect: \"Adds a fixed number of labelled route sequences to the private simulator.\",\n example: \"repeatDance(3);\",\n },\n {\n id: \"dance-rover-function-stop\",\n signature: \"emergencyStop()\",\n summary: \"Ends the simulated dance in a fail-safe stopped state.\",\n parameters: [],\n effect: \"Marks both motors stopped in the simulator; it cannot activate, stop or otherwise control physical hardware.\",\n example: \"emergencyStop();\",\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"dance-rover-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"dance-rover-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"dance-rover-m1-adult-hardware-guide\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"dance-rover-m1-protected-resilience\",\n statement: \"The simulator rejects unsupported directions, excessive speeds, waits, repeats, missing stop calls and physical-hardware requests.\",\n visibility: \"protected\",\n criterionIds: [\"dance-rover-edge-one\", \"dance-rover-edge-two\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to predict the labelled route and final stopped state before suggesting one bounded change.\",\n \"Use the function reference and visible telemetry; never provide motor wiring, power or movement advice to a learner.\",\n \"Physical export stays unavailable until an adult acknowledges the exact manifest and every driver, motor and power component has verified bench-test evidence.\",\n ],\n },\n hardware: {\n requirementsVersion: \"1.0.0\",\n hardwareIncluded: false,\n completePathItemIds: [\n \"pico-2-w\",\n \"breadboard\",\n \"usb-data-cable\",\n \"jumper-wires\",\n \"dual-motor-driver\",\n \"geared-motors\",\n \"rover-chassis\",\n \"motor-power\",\n ],\n incrementalItemIds: [\n \"dual-motor-driver\",\n \"geared-motors\",\n \"rover-chassis\",\n \"motor-power\",\n ],\n components: [\n { itemId: \"pico-2-w\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"breadboard\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"usb-data-cable\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"jumper-wires\", quantity: 12, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"dual-motor-driver\", quantity: 1, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"geared-motors\", quantity: 2, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"rover-chassis\", quantity: 1, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"motor-power\", quantity: 1, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n ],\n safeguards: {\n adultAssemblyRequired: true,\n adultAcknowledgementRequiredForExport: true,\n websiteMayControlHardware: false,\n simulatorCompletionAvailable: true,\n simulatedBadgeId: \"dance-rover-mission-complete\",\n physicalBadgeId: \"dance-rover-physical-builder\",\n physicalBadgeRequiresAdultSignoff: true,\n adultAssemblySteps: [\n \"Confirm the exact driver, motors, chassis and switched power identities against the requirements manifest.\",\n \"Assemble and inspect all wiring with motor power disconnected and secure every moving or pinch-point part.\",\n \"Complete the first direction and emergency-stop bench test with the wheels lifted clear of the surface.\",\n ],\n powerRequirements: [\n \"Use a switched protected motor supply within the verified driver and motor ratings; never power motors from a Pico GPIO pin.\",\n \"Connect one common signal ground between the verified motor supply, driver and Pico only as shown in the adult guide.\",\n \"Keep the power switch accessible and disconnect every source before changing wiring, wheels or chassis parts.\",\n ],\n cableRequirements: [\n \"One known data-capable USB cable compatible with the Pico 2 W.\",\n \"Insulated jumper leads and verified motor, driver and power connectors documented by the adult guide.\",\n ],\n softwarePrerequisites: [\n \"Supported Pico SDK toolchain on Raspberry Pi OS or a documented desktop environment.\",\n \"Known-good Dance Rover recovery firmware with adult-owned watchdog and emergency-stop behaviour.\",\n ],\n warnings: [\n \"Hardware is not included with the module.\",\n \"No listed driver, motor, chassis or power arrangement currently claims compatibility or physical-completion eligibility.\",\n \"Moving wheels, pinch points, stalled motors and unsuitable supplies can cause injury or heat; adult assembly and testing are mandatory.\",\n \"The simulator and simulated badge remain available without physical equipment.\",\n ],\n unrelatedHardwareNotRequired: [\n \"Camera Module 3 or Raspberry Pi Zero 2 W\",\n \"obstacle or colour sensors\",\n \"servo, LED or infrared beacon parts\",\n ],\n },\n },\n};\n\n/**\n * First Obstacle Explorer robotics mission. Learners use bounded simulated IR\n * readings, Boolean decisions, recovery state and a watchdog to plan a safe\n * route. Sensor input, firmware export and physical movement remain unavailable\n * until the exact reference build is calibrated and bench tested by an adult.\n */\nexport const OBSTACLE_EXPLORER_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.obstacle-explorer\",\n moduleVersion: \"1.1.0\",\n missionId: \"obstacle-explorer-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read what readObstacle(), chooseSafeRoute(), setRecoveryAttempts(), armWatchdog() and failSafeStop() do in the private Obstacle Explorer simulator.\",\n artifactIds: [\"obstacle-explorer-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict the simulated obstacle reading, safe route, recovery count, watchdog time and final stopped state before the explorer runs.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the five documented C++-style calls to make one bounded obstacle decision with a watchdog and fail-safe stop.\",\n artifactIds: [\"obstacle-explorer-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private Obstacle Explorer simulator.\",\n artifactIds: [\"obstacle-explorer-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic sensor, route, recovery, watchdog and stop checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted C++-style line with the first explorer goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one bounded sensor side, route, recovery count, watchdog or stop call, then rerun and inspect the text telemetry.\",\n artifactIds: [\"obstacle-explorer-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how a Boolean obstacle reading selected a route and why the watchdog and fail-safe stop protect every explorer state.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the simulated badge when the score and mandatory safety checks pass; physical completion remains adult-only.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"obstacle-explorer-m1-find-stop\",\n prompt: \"Find failSafeStop() and explain why the explorer must stop when a sensor or watchdog result is uncertain.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"obstacle-explorer-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"obstacle-explorer-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"obstacle-explorer-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"obstacle-explorer-m1-starts\",\n statement: \"The documented C++-style settings are valid and the private simulator starts.\",\n visibility: \"visible\",\n criterionIds: [\"obstacle-explorer-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"obstacle-explorer-m1-navigation\",\n statement: \"The explorer reads one simulated obstacle and chooses a bounded route with three recovery attempts.\",\n visibility: \"visible\",\n criterionIds: [\"obstacle-explorer-goal-one\", \"obstacle-explorer-goal-two\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"obstacle-explorer-m1-private-runtime\",\n statement: \"The program arms a watchdog, ends fail-safe stopped and never accesses sensors, motors, the network or browser storage.\",\n visibility: \"visible\",\n criterionIds: [\"obstacle-explorer-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"obstacle-explorer-m1-run-control\",\n description: \"Start the private Obstacle Explorer navigation simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"obstacle-explorer-m1-keyboard-run\"],\n },\n {\n id: \"obstacle-explorer-m1-code-control\",\n description: \"Edit the documented sensor, route, recovery, watchdog and stop calls.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"obstacle-explorer-m1-route-preview\",\n description: \"Observe the bounded obstacle decision, route and stopped state.\",\n primaryMode: \"motion\",\n alternativeIds: [\"obstacle-explorer-m1-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"obstacle-explorer-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same simulator.\",\n },\n {\n id: \"obstacle-explorer-m1-telemetry\",\n modes: [\"text\", \"shape\", \"symbol\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read sensor side, blocked state, route, recovery count, watchdog milliseconds and stopped state without animation or colour alone.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"obstacle-explorer-m1-assessment\",\n goalIds: [\n \"obstacle-explorer-m1-starts\",\n \"obstacle-explorer-m1-navigation\",\n \"obstacle-explorer-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"obstacle-explorer-m1-explanation\",\n goalIds: [\"obstacle-explorer-m1-navigation\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"obstacle-explorer-m1-remix\",\n prompt: \"Invent an original maze response and add a text or symbol cue that explains the Boolean decision without motion or colour alone.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"obstacle-explorer-m1-simulated-badge\",\n badgeId: \"obstacle-explorer-mission-complete\",\n goalIds: [\n \"obstacle-explorer-m1-starts\",\n \"obstacle-explorer-m1-navigation\",\n \"obstacle-explorer-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"obstacle-explorer-function-read\",\n signature: \"readObstacle(side)\",\n summary: \"Reads one labelled obstacle state from the private simulator.\",\n parameters: [\n {\n name: \"side\",\n type: \"string\",\n description: \"Use front, left or right.\",\n },\n ],\n effect: \"Returns a simulator Boolean blocked or clear reading and never accesses an IR sensor or GPIO pin.\",\n example: \"readObstacle(\\\"front\\\");\",\n },\n {\n id: \"obstacle-explorer-function-route\",\n signature: \"chooseSafeRoute(blockedAction, clearAction)\",\n summary: \"Chooses one bounded route for blocked and clear simulated states.\",\n parameters: [\n {\n name: \"blockedAction\",\n type: \"string\",\n description: \"Use turn-left, turn-right, back-up or stop.\",\n },\n {\n name: \"clearAction\",\n type: \"string\",\n description: \"Use forward or stop.\",\n },\n ],\n effect: \"Updates labelled simulator navigation state without energising motors or a driver.\",\n example: \"chooseSafeRoute(\\\"turn-left\\\", \\\"forward\\\");\",\n },\n {\n id: \"obstacle-explorer-function-recovery\",\n signature: \"setRecoveryAttempts(count)\",\n summary: \"Sets a bounded number of simulated recovery attempts.\",\n parameters: [\n {\n name: \"count\",\n type: \"whole number\",\n description: \"A bounded recovery count from 1 to 3.\",\n },\n ],\n effect: \"Limits the private simulator recovery state so an uncertain route cannot loop forever.\",\n example: \"setRecoveryAttempts(3);\",\n },\n {\n id: \"obstacle-explorer-function-watchdog\",\n signature: \"armWatchdog(duration)\",\n summary: \"Arms a bounded simulated watchdog timer.\",\n parameters: [\n {\n name: \"duration\",\n type: \"whole number\",\n description: \"A bounded timeout from 250 to 1000 milliseconds.\",\n },\n ],\n effect: \"Records simulator timeout telemetry and cannot hold or control physical movement.\",\n example: \"armWatchdog(500);\",\n },\n {\n id: \"obstacle-explorer-function-stop\",\n signature: \"failSafeStop()\",\n summary: \"Ends the navigation simulation in a fail-safe stopped state.\",\n parameters: [],\n effect: \"Marks the private simulator stopped on completion or uncertainty; it cannot activate, stop or otherwise control physical hardware.\",\n example: \"failSafeStop();\",\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"obstacle-explorer-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"obstacle-explorer-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"obstacle-explorer-m1-adult-hardware-guide\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"obstacle-explorer-m1-protected-resilience\",\n statement: \"The simulator rejects unsupported sensor sides, unsafe routes, excessive recovery attempts, invalid watchdogs, missing stop calls and physical-hardware requests.\",\n visibility: \"protected\",\n criterionIds: [\"obstacle-explorer-edge-one\", \"obstacle-explorer-edge-two\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to predict the Boolean blocked state, labelled route and final stopped state before suggesting one bounded change.\",\n \"Use the function reference and visible telemetry; never provide sensor wiring, motor power or movement advice to a learner.\",\n \"Physical export stays unavailable until an adult acknowledges the exact manifest and every rover and sensor component has calibration and bench-test evidence.\",\n ],\n },\n hardware: {\n requirementsVersion: \"1.0.0\",\n hardwareIncluded: false,\n completePathItemIds: [\n \"pico-2-w\",\n \"breadboard\",\n \"usb-data-cable\",\n \"jumper-wires\",\n \"verified-rover\",\n \"obstacle-sensors\",\n ],\n incrementalItemIds: [\n \"verified-rover\",\n \"obstacle-sensors\",\n ],\n components: [\n { itemId: \"pico-2-w\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"breadboard\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"usb-data-cable\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"jumper-wires\", quantity: 12, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"verified-rover\", quantity: 1, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"obstacle-sensors\", quantity: 2, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n ],\n safeguards: {\n adultAssemblyRequired: true,\n adultAcknowledgementRequiredForExport: true,\n websiteMayControlHardware: false,\n simulatorCompletionAvailable: true,\n simulatedBadgeId: \"obstacle-explorer-mission-complete\",\n physicalBadgeId: \"obstacle-explorer-physical-builder\",\n physicalBadgeRequiresAdultSignoff: true,\n adultAssemblySteps: [\n \"Confirm the exact bench-signed rover and IR sensor identities against the requirements manifest.\",\n \"Assemble and inspect all disconnected sensor wiring, then complete the adult sensor calibration record for clear and blocked surfaces.\",\n \"Complete direction, obstacle recovery, watchdog and fail-safe-stop tests with the wheels lifted clear of the surface.\",\n ],\n powerRequirements: [\n \"Use the verified switched protected motor supply and sensor voltage; never power motors or unsuitable sensors from a Pico GPIO pin.\",\n \"Connect one common signal ground between the verified sensor, motor supply, driver and Pico only as shown in the adult guide.\",\n \"Keep the power switch accessible and disconnect every source before changing wiring, sensors, wheels or chassis parts.\",\n ],\n cableRequirements: [\n \"One known data-capable USB cable compatible with the Pico 2 W.\",\n \"Insulated jumper leads and verified sensor, motor, driver and power connectors documented by the adult guide.\",\n ],\n softwarePrerequisites: [\n \"Supported Pico SDK toolchain on Raspberry Pi OS or a documented desktop environment.\",\n \"Known-good Obstacle Explorer recovery firmware with adult-owned watchdog, sensor-failure and emergency-stop behaviour.\",\n ],\n warnings: [\n \"Hardware is not included with the module.\",\n \"No listed rover or sensor currently claims compatibility or physical-completion eligibility for this module.\",\n \"Moving wheels, pinch points, stalled motors, reflective sensor errors and unsuitable supplies can cause unsafe movement or heat; adult assembly, calibration and testing are mandatory.\",\n \"The simulator and simulated badge remain available without physical equipment.\",\n ],\n unrelatedHardwareNotRequired: [\n \"Camera Module 3 or Raspberry Pi Zero 2 W\",\n \"colour targets or camera ribbon\",\n \"servo, LED or infrared beacon parts\",\n ],\n },\n },\n};\n\nexport const RAINBOW_RESCUE_ROVER_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.rainbow-rescue-rover\",\n moduleVersion: \"1.1.0\",\n missionId: \"rainbow-rescue-rover-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read what detectColour(), locateTarget(), planSerialCommand(), armHeartbeat() and failSafeStop() do in the private Rainbow Rescue Rover simulator.\",\n artifactIds: [\"rainbow-rescue-rover-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict the simulated colour, target zone, bounded command, heartbeat time and final stopped state before the rescue plan runs.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the five documented integration calls to recognise one simulated target and plan one safe serial command with a heartbeat and fail-safe stop.\",\n artifactIds: [\"rainbow-rescue-rover-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private Rainbow Rescue Rover integration simulator.\",\n artifactIds: [\"rainbow-rescue-rover-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic colour, location, command, heartbeat and stop checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted integration-plan line with the first rescue goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one bounded colour, target zone, command, heartbeat or stop call, then rerun and inspect the text telemetry.\",\n artifactIds: [\"rainbow-rescue-rover-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how family-local colour evidence became a bounded command plan and why the heartbeat and fail-safe stop protect every uncertain state.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the simulated badge when the score and mandatory privacy and safety checks pass; physical completion remains adult-only.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"rainbow-rescue-rover-m1-find-privacy\",\n prompt: \"Find the rule that keeps Camera Module 3 frames on the family Raspberry Pi and explain why only bounded command labels may leave it.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"rainbow-rescue-rover-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"rainbow-rescue-rover-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"rainbow-rescue-rover-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"rainbow-rescue-rover-m1-starts\",\n statement: \"The documented integration-plan settings are valid and the private simulator starts.\",\n visibility: \"visible\",\n criterionIds: [\"rainbow-rescue-rover-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"rainbow-rescue-rover-m1-target-command\",\n statement: \"The rover plan recognises a green simulated target in the centre and selects one bounded forward command.\",\n visibility: \"visible\",\n criterionIds: [\"rainbow-rescue-rover-goal-one\", \"rainbow-rescue-rover-goal-two\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"rainbow-rescue-rover-m1-private-runtime\",\n statement: \"The plan arms a heartbeat, ends fail-safe stopped and never accesses a camera, serial port, motor, network or browser storage.\",\n visibility: \"visible\",\n criterionIds: [\"rainbow-rescue-rover-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"rainbow-rescue-rover-m1-run-control\",\n description: \"Start the private Rainbow Rescue Rover integration simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"rainbow-rescue-rover-m1-keyboard-run\"],\n },\n {\n id: \"rainbow-rescue-rover-m1-code-control\",\n description: \"Edit the documented colour, target, command, heartbeat and stop calls.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"rainbow-rescue-rover-m1-target-preview\",\n description: \"Observe the simulated colour target, command route and stopped state.\",\n primaryMode: \"colour\",\n alternativeIds: [\"rainbow-rescue-rover-m1-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"rainbow-rescue-rover-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same simulator.\",\n },\n {\n id: \"rainbow-rescue-rover-m1-telemetry\",\n modes: [\"text\", \"shape\", \"symbol\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read the colour name, target zone, command, heartbeat milliseconds and stopped state without camera access, animation or colour alone.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"rainbow-rescue-rover-m1-assessment\",\n goalIds: [\n \"rainbow-rescue-rover-m1-starts\",\n \"rainbow-rescue-rover-m1-target-command\",\n \"rainbow-rescue-rover-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"rainbow-rescue-rover-m1-explanation\",\n goalIds: [\"rainbow-rescue-rover-m1-target-command\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"rainbow-rescue-rover-m1-remix\",\n prompt: \"Invent an original colour rescue rule and add a text, shape or symbol cue that explains the command without camera frames, motion or colour alone.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"rainbow-rescue-rover-m1-simulated-badge\",\n badgeId: \"rainbow-rescue-rover-mission-complete\",\n goalIds: [\n \"rainbow-rescue-rover-m1-starts\",\n \"rainbow-rescue-rover-m1-target-command\",\n \"rainbow-rescue-rover-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"rainbow-rescue-rover-function-detect\",\n signature: \"detectColour(colour)\",\n summary: \"Selects one labelled colour result in the private simulator.\",\n parameters: [\n {\n name: \"colour\",\n type: \"string\",\n description: \"Use red, green, blue or yellow.\",\n },\n ],\n effect: \"Returns one simulator colour label and confidence state; it never opens a camera or receives a frame.\",\n example: \"detectColour(\\\"green\\\");\",\n },\n {\n id: \"rainbow-rescue-rover-function-locate\",\n signature: \"locateTarget(zone)\",\n summary: \"Places the simulated target in one labelled horizontal zone.\",\n parameters: [\n {\n name: \"zone\",\n type: \"string\",\n description: \"Use left, centre or right.\",\n },\n ],\n effect: \"Updates text, shape and coordinate cues in the simulator without analysing or storing an image.\",\n example: \"locateTarget(\\\"centre\\\");\",\n },\n {\n id: \"rainbow-rescue-rover-function-command\",\n signature: \"planSerialCommand(command)\",\n summary: \"Plans one bounded command label for the simulated rover link.\",\n parameters: [\n {\n name: \"command\",\n type: \"string\",\n description: \"Use forward, turn-left, turn-right or stop.\",\n },\n ],\n effect: \"Records a simulator-only command label; it never opens a serial port or activates motors.\",\n example: \"planSerialCommand(\\\"forward\\\");\",\n },\n {\n id: \"rainbow-rescue-rover-function-heartbeat\",\n signature: \"armHeartbeat(duration)\",\n summary: \"Arms a bounded simulated command heartbeat.\",\n parameters: [\n {\n name: \"duration\",\n type: \"whole number\",\n description: \"A bounded heartbeat from 250 to 1000 milliseconds.\",\n },\n ],\n effect: \"Records heartbeat telemetry so an uncertain simulator link becomes stopped; it cannot maintain physical movement.\",\n example: \"armHeartbeat(500);\",\n },\n {\n id: \"rainbow-rescue-rover-function-stop\",\n signature: \"failSafeStop()\",\n summary: \"Ends the integration simulation in a fail-safe stopped state.\",\n parameters: [],\n effect: \"Marks the private simulator stopped after the bounded command plan; it cannot activate, stop or otherwise control physical hardware.\",\n example: \"failSafeStop();\",\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"rainbow-rescue-rover-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"rainbow-rescue-rover-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"rainbow-rescue-rover-m1-adult-hardware-guide\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"rainbow-rescue-rover-m1-protected-resilience\",\n statement: \"The simulator rejects unsupported colours, zones, serial commands, invalid heartbeats, missing stop calls and any camera, serial or physical-hardware request.\",\n visibility: \"protected\",\n criterionIds: [\"rainbow-rescue-rover-edge-one\", \"rainbow-rescue-rover-edge-two\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to predict the labelled target, bounded command and final stopped state before suggesting one change.\",\n \"Use only authored function guidance and simulator telemetry; never request camera frames or give learner wiring, power or motor-control advice.\",\n \"Physical export stays unavailable until an adult acknowledges the exact manifest and every camera, computer, serial, rover and power component has bench-test evidence.\",\n ],\n },\n hardware: {\n requirementsVersion: \"1.0.0\",\n hardwareIncluded: false,\n completePathItemIds: [\n \"pico-2-w\",\n \"breadboard\",\n \"usb-data-cable\",\n \"jumper-wires\",\n \"verified-explorer\",\n \"pi-zero-2-w\",\n \"camera-3\",\n \"pi-storage-power\",\n ],\n incrementalItemIds: [\n \"verified-explorer\",\n \"pi-zero-2-w\",\n \"camera-3\",\n \"pi-storage-power\",\n ],\n components: [\n { itemId: \"pico-2-w\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"breadboard\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"usb-data-cable\", quantity: 1, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"jumper-wires\", quantity: 12, acquisitionScope: \"complete-path\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"verified-explorer\", quantity: 1, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"pi-zero-2-w\", quantity: 1, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"camera-3\", quantity: 1, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n { itemId: \"pi-storage-power\", quantity: 1, acquisitionScope: \"incremental\", verificationStatus: \"pending-bench-test\", compatibilityClaimed: false, physicalCompletionEligible: false },\n ],\n safeguards: {\n adultAssemblyRequired: true,\n adultAcknowledgementRequiredForExport: true,\n websiteMayControlHardware: false,\n simulatorCompletionAvailable: true,\n simulatedBadgeId: \"rainbow-rescue-rover-mission-complete\",\n physicalBadgeId: \"rainbow-rescue-rover-physical-builder\",\n physicalBadgeRequiresAdultSignoff: true,\n adultAssemblySteps: [\n \"Confirm the exact bench-signed rover, Pi Zero 2 W, Camera Module 3 and correct Zero-series camera ribbon identities against the manifest.\",\n \"With all power disconnected, fit and inspect the camera ribbon, storage, Pi power, Pico data link and isolated rover assemblies using the adult guide.\",\n \"Complete local colour calibration, bounded serial-command, heartbeat, link-loss and fail-safe-stop tests with the wheels lifted clear of the surface.\",\n ],\n powerRequirements: [\n \"Use a separate regulated Raspberry Pi power supply for the Pi Zero 2 W and Camera Module 3.\",\n \"Use the verified switched protected motor supply for the rover; never power motors, the Pi Zero 2 W or Camera Module 3 from a Pico GPIO pin.\",\n \"Keep every power switch accessible and disconnect every source before changing the camera ribbon, storage, wiring, sensors, wheels or chassis parts.\",\n ],\n cableRequirements: [\n \"One known data-capable USB cable compatible with the Pico 2 W and Pi Zero 2 W serial plan.\",\n \"The correct Zero-series Camera Module 3 ribbon and adult-verified insulated rover and power connectors.\",\n ],\n softwarePrerequisites: [\n \"Current Raspberry Pi OS with supported rpicam and Picamera2 software on the family-owned Pi Zero 2 W.\",\n \"Supported Pico SDK toolchain and known-good recovery firmware with adult-owned heartbeat, link-loss and emergency-stop behaviour.\",\n \"A family-local colour-calibration utility that never uploads, publishes or transmits Camera Module 3 frames.\",\n ],\n warnings: [\n \"Hardware is not included with the module.\",\n \"No listed camera, computer, rover, serial or power configuration currently claims compatibility or physical-completion eligibility.\",\n \"Camera frames remain on the family Raspberry Pi and must never be submitted to Plasius, an agent service or a published project.\",\n \"The website never activates motors, opens a camera or serial port, and an uncertain or missing heartbeat must stop the physical rover.\",\n \"Moving wheels, pinch points, stalled motors, camera ribbon damage and unsuitable supplies can cause unsafe movement, heat or damage; adult assembly, calibration and testing are mandatory.\",\n \"The simulator and simulated badge remain available without physical equipment.\",\n ],\n unrelatedHardwareNotRequired: [\n \"cloud camera, object-recognition or face-recognition services\",\n \"microphone, speaker, location or biometric sensors\",\n \"public hosting, analytics, advertising or external network access\",\n ],\n },\n },\n};\n\n/**\n * Original first mission for Rescue Crew Commander. The learner arranges a\n * typed visual program and can inspect its synchronized JavaScript projection,\n * while protected route and action-limit checks stay facilitator-only.\n */\nexport const RESCUE_CREW_COMMANDER_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.rescue-crew-commander\",\n moduleVersion: \"1.1.0\",\n missionId: \"rescue-crew-commander-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Meet the helper, job, route and priority blocks and read what each block does in the synchronized JavaScript view.\",\n artifactIds: [\"rescue-crew-commander-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict which helper will act first and which safe route it will follow.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Arrange the visual blocks to give each helper one safe rescue job.\",\n artifactIds: [\"rescue-crew-commander-m1-program\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to watch the crew follow the typed visual program.\",\n artifactIds: [\"rescue-crew-commander-m1-program\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic crew checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted block with the first goal that did not pass and inspect the matching JavaScript line.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Move or replace one job, route or priority block, then run the mission again.\",\n artifactIds: [\"rescue-crew-commander-m1-program\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how job order and priority changed the crew state and rescue result.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge when the score and private-simulator safety check pass.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"rescue-crew-commander-m1-find-priority\",\n prompt: \"Point to the block that decides which helper acts first.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"rescue-crew-commander-m1-program\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"rescue-crew-commander-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"rescue-crew-commander-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"rescue-crew-commander-m1-starts\",\n statement: \"The typed visual program is structurally valid and starts.\",\n visibility: \"visible\",\n criterionIds: [\"rescue-crew-commander-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"rescue-crew-commander-m1-safe-jobs\",\n statement: \"Every helper receives one suitable job and the highest-priority rescue starts first.\",\n visibility: \"visible\",\n criterionIds: [\n \"rescue-crew-commander-goal-one\",\n \"rescue-crew-commander-goal-two\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"rescue-crew-commander-m1-private-runtime\",\n statement: \"The crew stays inside the private simulator and follows only host-provided actions.\",\n visibility: \"visible\",\n criterionIds: [\"rescue-crew-commander-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"rescue-crew-commander-m1-reorder-blocks\",\n description: \"Change the order of typed job, route and priority blocks.\",\n primaryMode: \"drag\",\n alternativeIds: [\"rescue-crew-commander-m1-button-reorder\"],\n },\n {\n id: \"rescue-crew-commander-m1-run-control\",\n description: \"Start the private rescue-crew simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"rescue-crew-commander-m1-keyboard-run\"],\n },\n {\n id: \"rescue-crew-commander-m1-crew-motion\",\n description: \"Observe helpers change state and follow their assigned routes.\",\n primaryMode: \"motion\",\n alternativeIds: [\"rescue-crew-commander-m1-status-view\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"rescue-crew-commander-m1-button-reorder\",\n modes: [\"keyboard\", \"pointer\"],\n equivalentOutcome: true,\n description: \"Use labelled Move up and Move down buttons instead of dragging a visual block.\",\n },\n {\n id: \"rescue-crew-commander-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same preview.\",\n },\n {\n id: \"rescue-crew-commander-m1-status-view\",\n modes: [\"text\", \"symbol\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read each helper's job, route, priority and state from the status list without animation or colour dependence.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"rescue-crew-commander-m1-assessment\",\n goalIds: [\n \"rescue-crew-commander-m1-starts\",\n \"rescue-crew-commander-m1-safe-jobs\",\n \"rescue-crew-commander-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"rescue-crew-commander-m1-explanation\",\n goalIds: [\"rescue-crew-commander-m1-safe-jobs\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"rescue-crew-commander-m1-remix\",\n prompt: \"Invent an original helper role and explain which safe route and priority it should receive.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"rescue-crew-commander-m1-badge\",\n badgeId: \"rescue-crew-commander-mission-complete\",\n goalIds: [\n \"rescue-crew-commander-m1-starts\",\n \"rescue-crew-commander-m1-safe-jobs\",\n \"rescue-crew-commander-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"rescue-crew-commander-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"rescue-crew-commander-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"rescue-crew-commander-m1-protected-resilience\",\n statement: \"The interpreter rejects unknown blocks, duplicate assignments and programs over the action limit.\",\n visibility: \"protected\",\n criterionIds: [\n \"rescue-crew-commander-edge-one\",\n \"rescue-crew-commander-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner which helper should act first before suggesting a block change.\",\n \"Use the block reference, status list and visible goal; never reveal the protected assignment order or expected block sequence.\",\n ],\n },\n};\n\n/**\n * Original first mission for Meteor Shield. Learners tune documented targeting,\n * energy and timing controls without receiving protected resource targets or\n * projectile answers.\n */\nexport const METEOR_SHIELD_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.meteor-shield\",\n moduleVersion: \"1.1.0\",\n missionId: \"meteor-shield-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how target column, shield energy and launch delay change a rescue defence.\",\n artifactIds: [\"meteor-shield-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict which rescue base the shield will protect first.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Adjust the three documented settings in the starter JavaScript.\",\n artifactIds: [\"meteor-shield-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private meteor-wave preview.\",\n artifactIds: [\"meteor-shield-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic defence checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted setting with the first goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one setting, run again and observe the energy and target telemetry.\",\n artifactIds: [\"meteor-shield-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how targeting and launch timing affected the remaining shield energy.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge when the score and safety check pass.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"meteor-shield-m1-find-energy\",\n prompt: \"Point to the setting that limits how many shields can launch.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"meteor-shield-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"meteor-shield-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"meteor-shield-m1-printable\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"meteor-shield-m1-starts\",\n statement: \"The JavaScript settings are valid and the private preview starts.\",\n visibility: \"visible\",\n criterionIds: [\"meteor-shield-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"meteor-shield-m1-resource-defence\",\n statement: \"A shield launches toward the selected meteor while keeping enough energy for the next wave.\",\n visibility: \"visible\",\n criterionIds: [\n \"meteor-shield-goal-one\",\n \"meteor-shield-goal-two\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"meteor-shield-m1-private-runtime\",\n statement: \"The game stays inside the private educational preview boundary.\",\n visibility: \"visible\",\n criterionIds: [\"meteor-shield-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"meteor-shield-m1-run-control\",\n description: \"Start the private meteor-wave simulation.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"meteor-shield-m1-keyboard-run\"],\n },\n {\n id: \"meteor-shield-m1-target-control\",\n description: \"Move the targeting reticle between rescue columns and launch a shield.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"meteor-shield-m1-wave-motion\",\n description: \"Observe meteors and shield pulses crossing the rescue zone.\",\n primaryMode: \"motion\",\n alternativeIds: [\"meteor-shield-m1-telemetry\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"meteor-shield-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same preview.\",\n },\n {\n id: \"meteor-shield-m1-telemetry\",\n modes: [\"text\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Use target column, wave, distance and energy text instead of projectile animation.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"meteor-shield-m1-assessment\",\n goalIds: [\n \"meteor-shield-m1-starts\",\n \"meteor-shield-m1-resource-defence\",\n \"meteor-shield-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"meteor-shield-m1-explanation\",\n goalIds: [\"meteor-shield-m1-resource-defence\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"meteor-shield-m1-remix\",\n prompt: \"Invent an original rescue-base signal and describe the safe game event that activates it.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"meteor-shield-m1-badge\",\n badgeId: \"meteor-shield-mission-complete\",\n goalIds: [\n \"meteor-shield-m1-starts\",\n \"meteor-shield-m1-resource-defence\",\n \"meteor-shield-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"meteor-shield-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"meteor-shield-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"meteor-shield-m1-protected-resilience\",\n statement: \"The runtime clamps unsafe resources and terminates bounded projectile simulations.\",\n visibility: \"protected\",\n criterionIds: [\n \"meteor-shield-edge-one\",\n \"meteor-shield-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner which setting controls a limited resource before suggesting a code edit.\",\n \"Use visible telemetry and the function reference; never reveal protected numeric targets or expected source fragments.\",\n ],\n },\n};\n\n/** Bounded first Vibe mission; no open prompt or provider is required. */\nexport const VIBE_GAME_REMIX_LAB_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.vibe-game-remix-lab\",\n moduleVersion: \"1.1.0\",\n missionId: \"vibe-game-remix-lab-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how setRescueSpeed(), setGateSpacing() and setGoalCount() change the supplied mini-game.\",\n artifactIds: [\"vibe-game-remix-lab-m1-guide\"],\n },\n {\n kind: \"predict\",\n instruction: \"Choose one bounded intent card and predict what its one-line diff will change before viewing it.\",\n artifactIds: [\"vibe-game-remix-lab-m1-intent-cards\"],\n },\n {\n kind: \"build\",\n instruction: \"Open the supplied mini-game and keep changes inside its documented settings file.\",\n artifactIds: [\"vibe-game-remix-lab-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to start the private JavaScript preview.\",\n artifactIds: [\"vibe-game-remix-lab-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run deterministic checks before requesting or applying any suggestion.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the single authored diff with your prediction and the failed goal evidence.\",\n artifactIds: [\"vibe-game-remix-lab-m1-code\"],\n },\n {\n kind: \"fix\",\n instruction: \"Accept or reject the proposed change yourself, then rerun the preview and assessment.\",\n artifactIds: [\"vibe-game-remix-lab-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain why you accepted or rejected the change and what the new evidence shows.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge after the deterministic score reaches 80 and every safety check passes.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"vibe-game-remix-lab-m1-find-setting\",\n prompt: \"Point to the documented function that changes how many rescue goals appear.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"vibe-game-remix-lab-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"vibe-game-remix-lab-m1-guide\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"vibe-game-remix-lab-m1-intent-cards\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"vibe-game-remix-lab-m1-starts\",\n statement: \"The supplied JavaScript mini-game remains structurally valid and starts.\",\n visibility: \"visible\",\n criterionIds: [\"vibe-game-remix-lab-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"vibe-game-remix-lab-m1-bounded-remix\",\n statement: \"The approved one-file change matches the chosen intent and every published behaviour goal.\",\n visibility: \"visible\",\n criterionIds: [\n \"vibe-game-remix-lab-goal-one\",\n \"vibe-game-remix-lab-goal-two\",\n \"vibe-game-remix-lab-goal-three\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"vibe-game-remix-lab-m1-private-runtime\",\n statement: \"The remix stays inside the private sandbox with no network, personal data or automatic code changes.\",\n visibility: \"visible\",\n criterionIds: [\"vibe-game-remix-lab-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"vibe-game-remix-lab-m1-run-control\",\n description: \"Start the supplied mini-game preview.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-game-remix-lab-m1-keyboard-run\"],\n },\n {\n id: \"vibe-game-remix-lab-m1-diff-review\",\n description: \"Read the removed and added source line before choosing what to do.\",\n primaryMode: \"text\",\n alternativeIds: [],\n },\n {\n id: \"vibe-game-remix-lab-m1-accept-control\",\n description: \"Approve the exact immutable suggestion snapshot.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-game-remix-lab-m1-keyboard-review\"],\n },\n {\n id: \"vibe-game-remix-lab-m1-reject-control\",\n description: \"Reject the suggestion and preserve the current source.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-game-remix-lab-m1-keyboard-review\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"vibe-game-remix-lab-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same preview.\",\n },\n {\n id: \"vibe-game-remix-lab-m1-keyboard-review\",\n modes: [\"keyboard\", \"text\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read the labelled removed and added lines, then focus Accept or Reject and press Enter or Space.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"vibe-game-remix-lab-m1-assessment\",\n goalIds: [\n \"vibe-game-remix-lab-m1-starts\",\n \"vibe-game-remix-lab-m1-bounded-remix\",\n \"vibe-game-remix-lab-m1-private-runtime\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"vibe-game-remix-lab-m1-explanation\",\n goalIds: [\"vibe-game-remix-lab-m1-bounded-remix\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"vibe-game-remix-lab-m1-inventor\",\n prompt: \"Write a new bounded remix intent card with one permitted setting, one constraint and one success test.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"vibe-game-remix-lab-m1-badge\",\n badgeId: \"vibe-game-remix-lab-mission-complete\",\n goalIds: [\n \"vibe-game-remix-lab-m1-starts\",\n \"vibe-game-remix-lab-m1-bounded-remix\",\n \"vibe-game-remix-lab-m1-private-runtime\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"vibe-game-remix-lab-function-speed\",\n signature: \"setRescueSpeed(speed)\",\n summary: \"Sets the supplied rescue robot's bounded movement speed.\",\n parameters: [{ name: \"speed\", type: \"whole number\", description: \"A safe speed from 1 to 5.\" }],\n effect: \"Changes only the private mini-game simulation speed.\",\n example: \"setRescueSpeed(3);\",\n },\n {\n id: \"vibe-game-remix-lab-function-spacing\",\n signature: \"setGateSpacing(spacing)\",\n summary: \"Sets the gap between original rescue gates.\",\n parameters: [{ name: \"spacing\", type: \"whole number\", description: \"A bounded spacing from 2 to 6.\" }],\n effect: \"Changes only the generated gate layout in the private preview.\",\n example: \"setGateSpacing(4);\",\n },\n {\n id: \"vibe-game-remix-lab-function-goals\",\n signature: \"setGoalCount(count)\",\n summary: \"Chooses how many fictional rescue goals the round contains.\",\n parameters: [{ name: \"count\", type: \"whole number\", description: \"A bounded goal count from 1 to 4.\" }],\n effect: \"Changes the labelled rescue-goal count without network or account access.\",\n example: \"setGoalCount(3);\",\n },\n ],\n boundedSuggestion: {\n id: \"vibe-game-remix-lab-m1-authored-goal-diff\",\n source: \"authored-fallback\",\n intent: \"Make the round contain one more rescue goal.\",\n constraints: [\n \"Change exactly one documented setting.\",\n \"Keep the goal count inside the published range.\",\n \"Do not add network, storage, DOM or account access.\",\n ],\n permittedArtifactId: \"vibe-game-remix-lab-m1-code\",\n originalSnippet: \"setGoalCount(2);\",\n replacementSnippet: \"setGoalCount(3);\",\n explanationPrompt: \"Did the new goal count match your prediction, and which assessment evidence proves it?\",\n aiOptional: false,\n learnerApprovalRequired: true,\n alternatives: [\"accept\", \"reject\"],\n },\n },\n facilitator: {\n artifacts: [\n {\n id: \"vibe-game-remix-lab-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"vibe-game-remix-lab-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"vibe-game-remix-lab-m1-safety-notes\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"vibe-game-remix-lab-m1-protected-resilience\",\n statement: \"The sandbox rejects prompt injection, answer dumping, disallowed files, network access and changes outside the approved diff.\",\n visibility: \"protected\",\n criterionIds: [\"vibe-game-remix-lab-edge-one\", \"vibe-game-remix-lab-edge-two\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask for the learner's prediction before revealing the authored diff.\",\n \"Do not invite free-form chat; keep intent, evidence and suggestions bound to the current project, rubric and permitted artifact.\",\n \"A rejection must leave source unchanged, and AI/provider failure must never block deterministic completion.\",\n ],\n },\n};\n\n/** Evidence-led Vibe repair mission; no open prompt or provider is required. */\nexport const VIBE_BUG_DETECTIVE_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.vibe-bug-detective\",\n moduleVersion: \"1.1.0\",\n missionId: \"vibe-bug-detective-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how setRobotDirection(), setRobotSteps() and placeRescueBeacon() control the supplied mini-game.\",\n artifactIds: [\"vibe-bug-detective-m1-guide\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict why the robot moves away from the beacon before viewing the one-line repair.\",\n artifactIds: [\"vibe-bug-detective-m1-evidence-card\"],\n },\n {\n kind: \"build\",\n instruction: \"Open the intentionally broken mini-game without changing files outside its documented settings artifact.\",\n artifactIds: [\"vibe-bug-detective-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to reproduce the bug in the private JavaScript preview.\",\n artifactIds: [\"vibe-bug-detective-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run deterministic checks to collect failure evidence before reviewing any suggested fix.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare observed leftward movement with the expected right-side beacon goal and the single authored diff.\",\n artifactIds: [\"vibe-bug-detective-m1-code\"],\n },\n {\n kind: \"fix\",\n instruction: \"Accept or reject the exact direction repair yourself, then rerun every regression check.\",\n artifactIds: [\"vibe-bug-detective-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain which evidence identified the bug and why the minimal change fixed it without changing other behaviour.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge after the deterministic score reaches 80 and every safety check passes.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"vibe-bug-detective-m1-read-evidence\",\n prompt: \"Point to the observed direction and the beacon position before choosing a repair.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"vibe-bug-detective-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"vibe-bug-detective-m1-guide\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"vibe-bug-detective-m1-evidence-card\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"vibe-bug-detective-m1-starts\",\n statement: \"The intentionally broken JavaScript mini-game remains structurally valid and starts.\",\n visibility: \"visible\",\n criterionIds: [\"vibe-bug-detective-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"vibe-bug-detective-m1-repair\",\n statement: \"The robot travels three steps toward the right-side rescue beacon after one minimal direction repair.\",\n visibility: \"visible\",\n criterionIds: [\n \"vibe-bug-detective-goal-one\",\n \"vibe-bug-detective-goal-two\",\n \"vibe-bug-detective-goal-three\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"vibe-bug-detective-m1-regression-safety\",\n statement: \"The repair preserves the bounded step and beacon settings inside the private sandbox.\",\n visibility: \"visible\",\n criterionIds: [\"vibe-bug-detective-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"vibe-bug-detective-m1-run-control\",\n description: \"Reproduce the supplied mini-game bug in the private preview.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-bug-detective-m1-keyboard-run\"],\n },\n {\n id: \"vibe-bug-detective-m1-diff-review\",\n description: \"Read the labelled removed and added direction lines beside the assessment evidence.\",\n primaryMode: \"text\",\n alternativeIds: [],\n },\n {\n id: \"vibe-bug-detective-m1-accept-control\",\n description: \"Approve the exact immutable repair snapshot.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-bug-detective-m1-keyboard-review\"],\n },\n {\n id: \"vibe-bug-detective-m1-reject-control\",\n description: \"Reject the repair and preserve the current broken source.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-bug-detective-m1-keyboard-review\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"vibe-bug-detective-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to reproduce the same bug.\",\n },\n {\n id: \"vibe-bug-detective-m1-keyboard-review\",\n modes: [\"keyboard\", \"text\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read the text evidence and labelled diff, then focus Accept or Reject and press Enter or Space.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"vibe-bug-detective-m1-assessment\",\n goalIds: [\n \"vibe-bug-detective-m1-starts\",\n \"vibe-bug-detective-m1-repair\",\n \"vibe-bug-detective-m1-regression-safety\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"vibe-bug-detective-m1-explanation\",\n goalIds: [\"vibe-bug-detective-m1-repair\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"vibe-bug-detective-m1-regression-inventor\",\n prompt: \"Invent one extra regression test that proves the robot still stops at the rescue beacon.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"vibe-bug-detective-m1-badge\",\n badgeId: \"vibe-bug-detective-mission-complete\",\n goalIds: [\n \"vibe-bug-detective-m1-starts\",\n \"vibe-bug-detective-m1-repair\",\n \"vibe-bug-detective-m1-regression-safety\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"vibe-bug-detective-function-direction\",\n signature: \"setRobotDirection(direction)\",\n summary: \"Chooses the horizontal direction used by the supplied rescue robot.\",\n parameters: [{ name: \"direction\", type: \"text\", description: \"Use left or right.\" }],\n effect: \"Changes only the labelled movement direction in the private mini-game.\",\n example: \"setRobotDirection(\\\"right\\\");\",\n },\n {\n id: \"vibe-bug-detective-function-steps\",\n signature: \"setRobotSteps(count)\",\n summary: \"Chooses how many bounded grid steps the robot attempts.\",\n parameters: [{ name: \"count\", type: \"whole number\", description: \"A bounded count from 1 to 4.\" }],\n effect: \"Changes the private preview path length without controlling physical hardware.\",\n example: \"setRobotSteps(3);\",\n },\n {\n id: \"vibe-bug-detective-function-beacon\",\n signature: \"placeRescueBeacon(position)\",\n summary: \"Places the fictional rescue beacon on one labelled side.\",\n parameters: [{ name: \"position\", type: \"text\", description: \"Use left or right.\" }],\n effect: \"Changes only the fictional beacon position in the private preview.\",\n example: \"placeRescueBeacon(\\\"right\\\");\",\n },\n ],\n boundedSuggestion: {\n id: \"vibe-bug-detective-m1-authored-direction-repair\",\n source: \"authored-fallback\",\n intent: \"Make the robot move toward the right-side rescue beacon.\",\n constraints: [\n \"Change exactly one documented direction setting.\",\n \"Preserve the step count and beacon position.\",\n \"Do not add network, storage, DOM, account or physical hardware access.\",\n ],\n permittedArtifactId: \"vibe-bug-detective-m1-code\",\n originalSnippet: \"setRobotDirection(\\\"left\\\");\",\n replacementSnippet: \"setRobotDirection(\\\"right\\\");\",\n explanationPrompt: \"Which observed-versus-expected evidence identified the direction bug, and which regression result proves the repair?\",\n aiOptional: false,\n learnerApprovalRequired: true,\n alternatives: [\"accept\", \"reject\"],\n },\n },\n facilitator: {\n artifacts: [\n {\n id: \"vibe-bug-detective-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"vibe-bug-detective-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"vibe-bug-detective-m1-safety-notes\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"vibe-bug-detective-m1-protected-regressions\",\n statement: \"The repaired sandbox rejects extra statements, disallowed values, prompt injection, answer dumping, network access and changes outside the approved diff.\",\n visibility: \"protected\",\n criterionIds: [\"vibe-bug-detective-edge-one\", \"vibe-bug-detective-edge-two\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to describe observed and expected directions before revealing the authored repair.\",\n \"Keep every diagnostic choice tied to the current failing goal and permitted artifact; never invite free-form chat.\",\n \"A rejection must preserve the broken source, and AI/provider failure must never block deterministic repair or regression checks.\",\n ],\n },\n};\n\n/** Goal-led Vibe prototype mission; all idea choices and changes are bounded. */\nexport const VIBE_IDEA_STUDIO_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.vibe-idea-studio\",\n moduleVersion: \"1.1.0\",\n missionId: \"vibe-idea-studio-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how choosePrototype(), setStarCount() and setSuccessMessage() shape the bounded rescue-card template.\",\n artifactIds: [\"vibe-idea-studio-m1-guide\"],\n },\n {\n kind: \"predict\",\n instruction: \"Choose one idea, audience and acceptance test card, then predict what the one-line prototype change will show.\",\n artifactIds: [\"vibe-idea-studio-m1-idea-cards\"],\n },\n {\n kind: \"build\",\n instruction: \"Open the supplied template and keep every change inside its documented prototype settings artifact.\",\n artifactIds: [\"vibe-idea-studio-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the Run action button to preview the current rescue card in the private JavaScript simulator.\",\n artifactIds: [\"vibe-idea-studio-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run deterministic checks against the selected goal and acceptance test before viewing a suggestion.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the exact one-line diff with your prediction, chosen goal and failed acceptance-test evidence.\",\n artifactIds: [\"vibe-idea-studio-m1-code\"],\n },\n {\n kind: \"fix\",\n instruction: \"Accept or reject the immutable prototype change yourself, then rerun the preview and tests.\",\n artifactIds: [\"vibe-idea-studio-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how the final evidence proves the prototype meets the selected idea, audience and success test.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge after the deterministic score reaches 80 and every safety check passes.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"vibe-idea-studio-m1-match-test\",\n prompt: \"Match the three-star acceptance test to the documented setting that controls star count.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"vibe-idea-studio-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"vibe-idea-studio-m1-guide\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"vibe-idea-studio-m1-idea-cards\",\n kind: \"printable\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"vibe-idea-studio-m1-starts\",\n statement: \"The supplied JavaScript rescue-card template remains structurally valid and starts.\",\n visibility: \"visible\",\n criterionIds: [\"vibe-idea-studio-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"vibe-idea-studio-m1-prototype-goal\",\n statement: \"The prototype is a space rescue card for a friendly robot crew with three stars and a visible success message.\",\n visibility: \"visible\",\n criterionIds: [\n \"vibe-idea-studio-goal-one\",\n \"vibe-idea-studio-goal-two\",\n \"vibe-idea-studio-goal-three\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"vibe-idea-studio-m1-private-boundary\",\n statement: \"The prototype stays inside the selected template, permitted artifact and private sandbox.\",\n visibility: \"visible\",\n criterionIds: [\"vibe-idea-studio-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"vibe-idea-studio-m1-idea-cards\",\n description: \"Choose one bounded idea, audience and acceptance-test card.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-idea-studio-m1-keyboard-cards\"],\n },\n {\n id: \"vibe-idea-studio-m1-run-control\",\n description: \"Start the private rescue-card preview.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-idea-studio-m1-keyboard-run\"],\n },\n {\n id: \"vibe-idea-studio-m1-diff-review\",\n description: \"Read the labelled removed and added star-count lines before deciding.\",\n primaryMode: \"text\",\n alternativeIds: [],\n },\n {\n id: \"vibe-idea-studio-m1-accept-control\",\n description: \"Approve the exact immutable prototype change.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-idea-studio-m1-keyboard-review\"],\n },\n {\n id: \"vibe-idea-studio-m1-reject-control\",\n description: \"Reject the suggestion and preserve the current source.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"vibe-idea-studio-m1-keyboard-review\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"vibe-idea-studio-m1-keyboard-cards\",\n modes: [\"keyboard\", \"text\"],\n equivalentOutcome: true,\n description: \"Use labelled radio-card controls with arrow keys and Space to choose the same bounded goal.\",\n },\n {\n id: \"vibe-idea-studio-m1-keyboard-run\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the play-icon Run button to start the same preview.\",\n },\n {\n id: \"vibe-idea-studio-m1-keyboard-review\",\n modes: [\"keyboard\", \"text\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Read the labelled diff and acceptance evidence, then focus Accept or Reject and press Enter or Space.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"vibe-idea-studio-m1-assessment\",\n goalIds: [\n \"vibe-idea-studio-m1-starts\",\n \"vibe-idea-studio-m1-prototype-goal\",\n \"vibe-idea-studio-m1-private-boundary\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"vibe-idea-studio-m1-explanation\",\n goalIds: [\"vibe-idea-studio-m1-prototype-goal\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"vibe-idea-studio-m1-test-inventor\",\n prompt: \"Write one new bounded audience card and one matching acceptance test without changing the template boundary.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"vibe-idea-studio-m1-badge\",\n badgeId: \"vibe-idea-studio-mission-complete\",\n goalIds: [\n \"vibe-idea-studio-m1-starts\",\n \"vibe-idea-studio-m1-prototype-goal\",\n \"vibe-idea-studio-m1-private-boundary\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"vibe-idea-studio-function-prototype\",\n signature: \"choosePrototype(kind)\",\n summary: \"Chooses one supplied, age-appropriate interactive prototype template.\",\n parameters: [{ name: \"kind\", type: \"text\", description: \"Use rescue-card, creature-card or mission-sign.\" }],\n effect: \"Changes only the labelled template in the private preview.\",\n example: \"choosePrototype(\\\"rescue-card\\\");\",\n },\n {\n id: \"vibe-idea-studio-function-stars\",\n signature: \"setStarCount(count)\",\n summary: \"Chooses how many decorative success stars appear on the card.\",\n parameters: [{ name: \"count\", type: \"whole number\", description: \"A bounded count from 1 to 4.\" }],\n effect: \"Changes only the visible star count in the private preview.\",\n example: \"setStarCount(3);\",\n },\n {\n id: \"vibe-idea-studio-function-message\",\n signature: \"setSuccessMessage(message)\",\n summary: \"Chooses one supplied child-safe success message.\",\n parameters: [{ name: \"message\", type: \"text\", description: \"Use Mission ready!, Great teamwork! or Rescue complete!\" }],\n effect: \"Changes only the fictional card message and never sends or stores text.\",\n example: \"setSuccessMessage(\\\"Mission ready!\\\");\",\n },\n ],\n boundedSuggestion: {\n id: \"vibe-idea-studio-m1-authored-star-diff\",\n source: \"authored-fallback\",\n intent: \"Meet the selected acceptance test by showing three stars.\",\n constraints: [\n \"Change exactly one documented star-count setting.\",\n \"Preserve the selected prototype and supplied success message.\",\n \"Do not add free-form content, network, storage, DOM, account or hardware access.\",\n ],\n permittedArtifactId: \"vibe-idea-studio-m1-code\",\n originalSnippet: \"setStarCount(2);\",\n replacementSnippet: \"setStarCount(3);\",\n explanationPrompt: \"Did the accepted change satisfy the three-star acceptance test, and which evidence proves it?\",\n aiOptional: false,\n learnerApprovalRequired: true,\n alternatives: [\"accept\", \"reject\"],\n },\n },\n facilitator: {\n artifacts: [\n {\n id: \"vibe-idea-studio-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"vibe-idea-studio-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"vibe-idea-studio-m1-safety-notes\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"vibe-idea-studio-m1-protected-boundaries\",\n statement: \"The sandbox rejects free-form prompts, personal data, disallowed messages, extra statements, network access and changes outside the approved diff.\",\n visibility: \"protected\",\n criterionIds: [\"vibe-idea-studio-edge-one\", \"vibe-idea-studio-edge-two\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to name the idea, audience and acceptance test before revealing the authored diff.\",\n \"Keep choices bound to supplied child-safe cards, the current rubric and permitted artifact; never invite free-form chat.\",\n \"A rejection must preserve source, and AI/provider failure must never block deterministic prototype completion.\",\n ],\n },\n};\n\n/** Accessible fictional care dashboard with bounded component and timer state. */\nexport const CREATURE_CARE_DASHBOARD_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.creature-care-dashboard\",\n moduleVersion: \"1.1.0\",\n missionId: \"creature-care-dashboard-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how components, events, status displays, timers, responsive layout and reduced motion build an accessible fictional creature dashboard.\",\n artifactIds: [\"creature-care-dashboard-m1-guide\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict which parts of the status card should update and which movement should stop when reduced motion is enabled.\",\n artifactIds: [\"creature-care-dashboard-m1-status-cards\"],\n },\n {\n kind: \"build\",\n instruction: \"Change only the supplied creature, care status, timer, layout and reduced-motion settings.\",\n artifactIds: [\"creature-care-dashboard-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the action-icon Run button to update the private dashboard preview and its equivalent text status.\",\n artifactIds: [\"creature-care-dashboard-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run deterministic component, event, timer, status, responsive-layout and accessibility checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the observed dashboard state with the selected status card and highlighted reduced-motion setting.\",\n artifactIds: [\"creature-care-dashboard-m1-code\"],\n },\n {\n kind: \"fix\",\n instruction: \"Review and accept or reject the exact reduced-motion change yourself, then rerun every check.\",\n artifactIds: [\"creature-care-dashboard-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how events update component state, how the timer stays bounded and why reduced motion matters.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound care badge after the score reaches 80 and every privacy and accessibility check passes.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"creature-care-dashboard-m1-status-check\",\n prompt: \"Find the creature card, care status, timer and text summary that must all describe the same private state.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"creature-care-dashboard-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"creature-care-dashboard-m1-guide\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"creature-care-dashboard-m1-status-cards\",\n kind: \"sample-data\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"creature-care-dashboard-m1-starts\",\n statement: \"The responsive creature-card component, labelled status display and bounded timer state are valid and start.\",\n visibility: \"visible\",\n criterionIds: [\"creature-care-dashboard-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"creature-care-dashboard-m1-behaviour\",\n statement: \"A supplied event updates the fictional creature status and timer while the responsive dashboard keeps one consistent state.\",\n visibility: \"visible\",\n criterionIds: [\n \"creature-care-dashboard-goal-one\",\n \"creature-care-dashboard-goal-two\",\n \"creature-care-dashboard-goal-three\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"creature-care-dashboard-m1-accessible-private\",\n statement: \"The dashboard enables reduced motion, keeps an equivalent text status and uses no network, personal data or real schedule.\",\n visibility: \"visible\",\n criterionIds: [\"creature-care-dashboard-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"creature-care-dashboard-m1-editor\",\n description: \"Edit the five documented dashboard calls using keyboard or pointer controls.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"creature-care-dashboard-m1-run\",\n description: \"Activate the labelled action-icon Run control.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"creature-care-dashboard-m1-keyboard-run\"],\n },\n {\n id: \"creature-care-dashboard-m1-review\",\n description: \"Review and accept or reject the exact labelled diff.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"creature-care-dashboard-m1-keyboard-run\",\n description: \"Focus the Run action button and press Enter or Space to produce the same dashboard and text telemetry.\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n },\n ],\n evidenceRequirements: [\n {\n id: \"creature-care-dashboard-m1-assessment\",\n goalIds: [\n \"creature-care-dashboard-m1-starts\",\n \"creature-care-dashboard-m1-behaviour\",\n \"creature-care-dashboard-m1-accessible-private\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"creature-care-dashboard-m1-explanation\",\n goalIds: [\"creature-care-dashboard-m1-accessible-private\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"creature-care-dashboard-m1-remix\",\n prompt: \"Choose another supplied creature, status and responsive layout while preserving the timer and reduced-motion evidence.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"creature-care-dashboard-m1-badge\",\n badgeId: \"creature-care-dashboard-mission-complete\",\n goalIds: [\n \"creature-care-dashboard-m1-starts\",\n \"creature-care-dashboard-m1-behaviour\",\n \"creature-care-dashboard-m1-accessible-private\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"creature-care-dashboard-function-creature\",\n signature: \"chooseCreature(creature)\",\n summary: \"Chooses one supplied fictional creature for the main component card.\",\n parameters: [{ name: \"creature\", type: \"text\", description: \"Use Moon Moth, Cloud Cat or Pebble Dragon.\" }],\n effect: \"Updates the creature card heading, illustration label and equivalent text status.\",\n example: \"chooseCreature(\\\"Moon Moth\\\");\",\n },\n {\n id: \"creature-care-dashboard-function-status\",\n signature: \"setCareStatus(status)\",\n summary: \"Chooses one supplied care state for the fictional creature.\",\n parameters: [{ name: \"status\", type: \"text\", description: \"Use Resting, Ready to play or Snack time.\" }],\n effect: \"Updates the visible component status and accessible live-status text.\",\n example: \"setCareStatus(\\\"Ready to play\\\");\",\n },\n {\n id: \"creature-care-dashboard-function-timer\",\n signature: \"setCareTimer(seconds)\",\n summary: \"Sets a short simulated care timer from a supplied safe value.\",\n parameters: [{ name: \"seconds\", type: \"number\", description: \"Use 5, 10 or 15 simulated seconds.\" }],\n effect: \"Updates bounded timer state and text telemetry without scheduling background work.\",\n example: \"setCareTimer(10);\",\n },\n {\n id: \"creature-care-dashboard-function-layout\",\n signature: \"setDashboardLayout(layout)\",\n summary: \"Chooses one supplied responsive card arrangement.\",\n parameters: [{ name: \"layout\", type: \"text\", description: \"Use single, cosy-grid or wide-grid.\" }],\n effect: \"Changes the simulated preview layout while keeping the same reading and keyboard order.\",\n example: \"setDashboardLayout(\\\"cosy-grid\\\");\",\n },\n {\n id: \"creature-care-dashboard-function-motion\",\n signature: \"setReducedMotion(enabled)\",\n summary: \"Turns the reduced-motion presentation on or off.\",\n parameters: [{ name: \"enabled\", type: \"Boolean\", description: \"Use true to stop decorative movement.\" }],\n effect: \"Disables decorative preview animation while preserving status, timer and event feedback.\",\n example: \"setReducedMotion(true);\",\n },\n ],\n boundedSuggestion: {\n id: \"creature-care-dashboard-m1-reduced-motion-diff\",\n source: \"authored-fallback\",\n intent: \"Keep the fictional care dashboard understandable without decorative movement.\",\n constraints: [\n \"Change exactly one documented reduced-motion setting.\",\n \"Preserve the supplied creature, status, timer, responsive layout and text telemetry.\",\n \"Do not add network, external scripts, trackers, personal data, real schedules or background tasks.\",\n ],\n permittedArtifactId: \"creature-care-dashboard-m1-code\",\n originalSnippet: \"setReducedMotion(false);\",\n replacementSnippet: \"setReducedMotion(true);\",\n explanationPrompt: \"Which decorative movement stopped, and which event, timer and text evidence stayed available?\",\n aiOptional: false,\n learnerApprovalRequired: true,\n alternatives: [\"accept\", \"reject\"],\n },\n },\n facilitator: {\n artifacts: [\n {\n id: \"creature-care-dashboard-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"creature-care-dashboard-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"creature-care-dashboard-m1-safety-notes\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"creature-care-dashboard-m1-protected-boundary\",\n statement: \"The dashboard rejects arbitrary text, personal data, real schedules, scripts, network calls, tracking, unbounded timers and inaccessible motion-only feedback.\",\n visibility: \"protected\",\n criterionIds: [\n \"creature-care-dashboard-edge-one\",\n \"creature-care-dashboard-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to identify the component state, event result, timer and text-equivalent evidence before discussing reduced motion.\",\n \"Use only the supplied fictional creature and status cards; redirect names, real schedules and personal details to safe choices.\",\n \"Reject must preserve source, and provider failure must never block the deterministic authored path.\",\n ],\n },\n};\n\n/** Safe responsive mission-control dashboard backed only by a serial simulator. */\nexport const ROBOT_MISSION_CONTROL_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.robot-mission-control\",\n moduleVersion: \"1.1.0\",\n missionId: \"robot-mission-control-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how commands, a fail-safe state machine, confirmations, telemetry charts and the five documented functions build a simulated mission-control panel.\",\n artifactIds: [\"robot-mission-control-m1-guide\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict whether a planned command may leave STOP when the safety confirmation is disabled.\",\n artifactIds: [\"robot-mission-control-m1-command-cards\"],\n },\n {\n kind: \"build\",\n instruction: \"Change only the supplied command, confirmation, telemetry rate, chart mode and serial-simulation settings.\",\n artifactIds: [\"robot-mission-control-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the action-icon Run button to update the private simulated controls, chart and text telemetry.\",\n artifactIds: [\"robot-mission-control-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run deterministic command, state-machine, safety-confirmation, telemetry and responsive-layout checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the observed STOP state with the planned command and highlighted safety-confirmation setting.\",\n artifactIds: [\"robot-mission-control-m1-code\"],\n },\n {\n kind: \"fix\",\n instruction: \"Review and accept or reject the exact safety-confirmation change yourself, then rerun every check.\",\n artifactIds: [\"robot-mission-control-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how confirmation changes the state machine and why the chart and text telemetry must agree.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound mission-control badge after the score reaches 80 and every mandatory STOP test passes.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"robot-mission-control-m1-stop-check\",\n prompt: \"Find the planned command, current STOP state, confirmation and text telemetry before running the simulator.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"robot-mission-control-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"robot-mission-control-m1-guide\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"robot-mission-control-m1-command-cards\",\n kind: \"sample-data\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"robot-mission-control-m1-starts\",\n statement: \"The responsive control component, chart and text telemetry build with a valid simulated command state machine.\",\n visibility: \"visible\",\n criterionIds: [\"robot-mission-control-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"robot-mission-control-m1-behaviour\",\n statement: \"A confirmed command moves the state machine from STOP and updates the chart and serial simulation with matching bounded telemetry.\",\n visibility: \"visible\",\n criterionIds: [\n \"robot-mission-control-goal-one\",\n \"robot-mission-control-goal-two\",\n \"robot-mission-control-goal-three\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"robot-mission-control-m1-safe-private\",\n statement: \"Without confirmation the simulator stays in STOP, exposes responsive text telemetry and never opens a real serial port or controls hardware.\",\n visibility: \"visible\",\n criterionIds: [\"robot-mission-control-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"robot-mission-control-m1-editor\",\n description: \"Edit the five documented mission-control calls using keyboard or pointer controls.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n {\n id: \"robot-mission-control-m1-run\",\n description: \"Activate the labelled action-icon Run control.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"robot-mission-control-m1-keyboard-run\"],\n },\n {\n id: \"robot-mission-control-m1-review\",\n description: \"Review and accept or reject the exact labelled safety diff.\",\n primaryMode: \"keyboard\",\n alternativeIds: [],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"robot-mission-control-m1-keyboard-run\",\n description: \"Focus the Run action button and press Enter or Space to produce the same controls, chart and text telemetry.\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n },\n ],\n evidenceRequirements: [\n {\n id: \"robot-mission-control-m1-assessment\",\n goalIds: [\n \"robot-mission-control-m1-starts\",\n \"robot-mission-control-m1-behaviour\",\n \"robot-mission-control-m1-safe-private\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"robot-mission-control-m1-explanation\",\n goalIds: [\"robot-mission-control-m1-safe-private\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"robot-mission-control-m1-remix\",\n prompt: \"Choose another supplied command or chart mode and explain which confirmation and STOP evidence must remain.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"robot-mission-control-m1-badge\",\n badgeId: \"robot-mission-control-mission-complete\",\n goalIds: [\n \"robot-mission-control-m1-starts\",\n \"robot-mission-control-m1-behaviour\",\n \"robot-mission-control-m1-safe-private\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"robot-mission-control-function-command\",\n signature: \"planCommand(command)\",\n summary: \"Plans one supplied command for the simulated robot state machine.\",\n parameters: [{ name: \"command\", type: \"text\", description: \"Use scan, hold-position or return-to-base.\" }],\n effect: \"Updates the planned-command panel but cannot leave STOP without confirmation.\",\n example: \"planCommand(\\\"scan\\\");\",\n },\n {\n id: \"robot-mission-control-function-confirmation\",\n signature: \"setSafetyConfirmation(enabled)\",\n summary: \"Controls whether the planned simulated command has explicit safety confirmation.\",\n parameters: [{ name: \"enabled\", type: \"Boolean\", description: \"Use true only after checking the planned command.\" }],\n effect: \"Allows the simulator state machine to leave STOP; it never approves or controls physical hardware.\",\n example: \"setSafetyConfirmation(true);\",\n },\n {\n id: \"robot-mission-control-function-rate\",\n signature: \"setTelemetryRate(samplesPerSecond)\",\n summary: \"Sets a bounded simulated telemetry sampling rate.\",\n parameters: [{ name: \"samplesPerSecond\", type: \"number\", description: \"Use 1, 2 or 4 simulated samples per second.\" }],\n effect: \"Changes bounded chart and text sample spacing without starting background work.\",\n example: \"setTelemetryRate(2);\",\n },\n {\n id: \"robot-mission-control-function-chart\",\n signature: \"setChartMode(mode)\",\n summary: \"Chooses one supplied responsive telemetry presentation.\",\n parameters: [{ name: \"mode\", type: \"text\", description: \"Use line, bars or text-only.\" }],\n effect: \"Changes the preview chart while preserving equivalent labelled text telemetry.\",\n example: \"setChartMode(\\\"line\\\");\",\n },\n {\n id: \"robot-mission-control-function-serial\",\n signature: \"simulateSerial(enabled)\",\n summary: \"Turns the bounded serial-message simulator on or off.\",\n parameters: [{ name: \"enabled\", type: \"Boolean\", description: \"Use true to show simulated messages.\" }],\n effect: \"Produces labelled local simulator messages; it never opens Web Serial or a physical connection.\",\n example: \"simulateSerial(true);\",\n },\n ],\n boundedSuggestion: {\n id: \"robot-mission-control-m1-confirmation-diff\",\n source: \"authored-fallback\",\n intent: \"Confirm the supplied simulated command before the state machine leaves STOP.\",\n constraints: [\n \"Change exactly one documented safety-confirmation setting.\",\n \"Preserve the supplied command, telemetry rate, chart mode, serial simulation and text telemetry.\",\n \"Do not add Web Serial, network, external scripts, hardware control, personal data or automatic approval.\",\n ],\n permittedArtifactId: \"robot-mission-control-m1-code\",\n originalSnippet: \"setSafetyConfirmation(false);\",\n replacementSnippet: \"setSafetyConfirmation(true);\",\n explanationPrompt: \"Which state-machine transition became possible, and what keeps the exercise separate from physical hardware?\",\n aiOptional: false,\n learnerApprovalRequired: true,\n alternatives: [\"accept\", \"reject\"],\n },\n },\n facilitator: {\n artifacts: [\n {\n id: \"robot-mission-control-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"robot-mission-control-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"robot-mission-control-m1-safety-notes\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"robot-mission-control-m1-protected-boundary\",\n statement: \"The dashboard rejects arbitrary commands, invalid rates, scripts, network access, Web Serial, hardware control, personal data and any unconfirmed transition away from STOP.\",\n visibility: \"protected\",\n criterionIds: [\n \"robot-mission-control-edge-one\",\n \"robot-mission-control-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to identify the planned command, current state, confirmation and equivalent text telemetry before suggesting a change.\",\n \"Keep every command and message inside the simulator; the website never opens serial or controls hardware.\",\n \"Reject must preserve source, and provider failure must never block deterministic completion.\",\n ],\n },\n};\n\n/** Accessible fictional planner mission with a private bounded persistence simulator. */\nexport const ADVENTURE_MISSION_PLANNER_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.adventure-mission-planner\",\n moduleVersion: \"1.1.0\",\n missionId: \"adventure-mission-planner-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Read how semantic headings, labelled mission fields, arrays, state and the five documented functions build an accessible fictional planner.\",\n artifactIds: [\"adventure-mission-planner-m1-guide\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict what will still be available after the private preview restarts when local save is disabled.\",\n artifactIds: [\"adventure-mission-planner-m1-test-card\"],\n },\n {\n kind: \"build\",\n instruction: \"Change only the supplied planner settings and keep the fictional mission free of names, contact details and real locations.\",\n artifactIds: [\"adventure-mission-planner-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Use the action-icon Run button to render the semantic private planner preview and its text equivalent.\",\n artifactIds: [\"adventure-mission-planner-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run deterministic structure, validation, array, state, local-save and accessibility checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the observed restart result with the selected persistence goal and highlighted setting.\",\n artifactIds: [\"adventure-mission-planner-m1-code\"],\n },\n {\n kind: \"fix\",\n instruction: \"Review and accept or reject the exact local-save change yourself, then rerun every check.\",\n artifactIds: [\"adventure-mission-planner-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how semantic labels, validation and private local save make the planner easier and safer to use.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound planner badge after the score reaches 80 and every privacy and accessibility check passes.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"adventure-mission-planner-m1-label-check\",\n prompt: \"Find the visible heading, mission title, day and validation message that a screen reader must also announce.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"adventure-mission-planner-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"adventure-mission-planner-m1-guide\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"adventure-mission-planner-m1-test-card\",\n kind: \"sample-data\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"adventure-mission-planner-m1-starts\",\n statement: \"The semantic planner structure, labelled validation message and fictional mission array are valid and start.\",\n visibility: \"visible\",\n criterionIds: [\"adventure-mission-planner-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"adventure-mission-planner-m1-behaviour\",\n statement: \"The planner state contains the supplied fictional mission and its approved private local save survives a simulated restart.\",\n visibility: \"visible\",\n criterionIds: [\n \"adventure-mission-planner-goal-one\",\n \"adventure-mission-planner-goal-two\",\n \"adventure-mission-planner-goal-three\",\n ],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"adventure-mission-planner-m1-accessible-private\",\n statement: \"The planner keeps an accessible text summary and uses only private simulated local save with no network or personal data.\",\n visibility: \"visible\",\n criterionIds: [\"adventure-mission-planner-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"adventure-mission-planner-m1-editor\",\n description: \"Edit the documented planner settings.\",\n primaryMode: \"text\",\n alternativeIds: [],\n },\n {\n id: \"adventure-mission-planner-m1-run\",\n description: \"Run the private semantic planner preview.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"adventure-mission-planner-m1-keyboard-run\"],\n },\n {\n id: \"adventure-mission-planner-m1-review\",\n description: \"Read the labelled removed and added local-save lines and choose Accept or Reject.\",\n primaryMode: \"text\",\n alternativeIds: [],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"adventure-mission-planner-m1-keyboard-run\",\n modes: [\"keyboard\", \"text\", \"reduced-motion\"],\n equivalentOutcome: true,\n description: \"Press Enter or Space on the action-icon button and use the same text planner summary without motion or drag.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"adventure-mission-planner-m1-assessment\",\n goalIds: [\n \"adventure-mission-planner-m1-starts\",\n \"adventure-mission-planner-m1-behaviour\",\n \"adventure-mission-planner-m1-accessible-private\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"adventure-mission-planner-m1-explanation\",\n goalIds: [\"adventure-mission-planner-m1-accessible-private\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"adventure-mission-planner-m1-remix\",\n prompt: \"Add one supplied fictional mission and describe the validation and accessible summary it needs.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"adventure-mission-planner-m1-badge\",\n badgeId: \"adventure-mission-planner-mission-complete\",\n goalIds: [\n \"adventure-mission-planner-m1-starts\",\n \"adventure-mission-planner-m1-behaviour\",\n \"adventure-mission-planner-m1-accessible-private\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n functionReference: [\n {\n id: \"adventure-mission-planner-function-heading\",\n signature: \"setPlannerHeading(heading)\",\n summary: \"Chooses one supplied fictional heading for the semantic planner.\",\n parameters: [{ name: \"heading\", type: \"text\", description: \"Use Moonbase Missions, Forest Rescue Plans or Ocean Quest Board.\" }],\n effect: \"Updates the visible and screen-reader planner heading in the private preview.\",\n example: \"setPlannerHeading(\\\"Moonbase Missions\\\");\",\n },\n {\n id: \"adventure-mission-planner-function-mission\",\n signature: \"addMission(title, day)\",\n summary: \"Adds one supplied fictional mission to the in-memory mission array.\",\n parameters: [\n { name: \"title\", type: \"text\", description: \"Use a supplied fictional mission title.\" },\n { name: \"day\", type: \"text\", description: \"Use Saturday, Sunday or School holiday.\" },\n ],\n effect: \"Adds one validated fictional item to private preview state without sending a form.\",\n example: \"addMission(\\\"Find the moon crystal\\\", \\\"Saturday\\\");\",\n },\n {\n id: \"adventure-mission-planner-function-validation\",\n signature: \"showValidationMessage(message)\",\n summary: \"Chooses one supplied child-readable form validation message.\",\n parameters: [{ name: \"message\", type: \"text\", description: \"Use Choose a day, Add a mission title or Mission ready!\" }],\n effect: \"Shows the message visibly and in the accessible text summary.\",\n example: \"showValidationMessage(\\\"Mission ready!\\\");\",\n },\n {\n id: \"adventure-mission-planner-function-save\",\n signature: \"enableLocalSave(enabled)\",\n summary: \"Turns the private simulated local-save behaviour on or off.\",\n parameters: [{ name: \"enabled\", type: \"Boolean\", description: \"Use true or false.\" }],\n effect: \"Controls only the bounded simulated restart; it never writes Plasius or server storage.\",\n example: \"enableLocalSave(true);\",\n },\n {\n id: \"adventure-mission-planner-function-summary\",\n signature: \"setAccessibleSummary(enabled)\",\n summary: \"Keeps an equivalent text summary beside the visual planner.\",\n parameters: [{ name: \"enabled\", type: \"Boolean\", description: \"Use true to keep the equivalent summary.\" }],\n effect: \"Controls the private preview text equivalent used by assistive technology and reduced-motion routes.\",\n example: \"setAccessibleSummary(true);\",\n },\n ],\n boundedSuggestion: {\n id: \"adventure-mission-planner-m1-local-save-diff\",\n source: \"authored-fallback\",\n intent: \"Make the fictional mission survive the simulated private restart.\",\n constraints: [\n \"Change exactly one documented local-save setting.\",\n \"Preserve the supplied semantic heading, fictional mission, validation and accessible summary.\",\n \"Do not add network, external scripts, transmitting forms, trackers, personal data or server storage.\",\n ],\n permittedArtifactId: \"adventure-mission-planner-m1-code\",\n originalSnippet: \"enableLocalSave(false);\",\n replacementSnippet: \"enableLocalSave(true);\",\n explanationPrompt: \"Did the mission survive the simulated restart, and which accessibility and privacy evidence stayed unchanged?\",\n aiOptional: false,\n learnerApprovalRequired: true,\n alternatives: [\"accept\", \"reject\"],\n },\n },\n facilitator: {\n artifacts: [\n {\n id: \"adventure-mission-planner-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"adventure-mission-planner-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"adventure-mission-planner-m1-safety-notes\",\n kind: \"facilitator-note\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"adventure-mission-planner-m1-protected-boundary\",\n statement: \"The planner rejects arbitrary text, personal data, scripts, network calls, transmitting forms, tracking and persistence outside the bounded simulator.\",\n visibility: \"protected\",\n criterionIds: [\n \"adventure-mission-planner-edge-one\",\n \"adventure-mission-planner-edge-two\",\n ],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to name the semantic heading, validation message and text-equivalent evidence before discussing local save.\",\n \"Use only fictional supplied content; redirect names, contacts and real locations to the safe cards.\",\n \"Reject must preserve source, and provider failure must never block the deterministic authored path.\",\n ],\n },\n};\n\nexport const ROAD_HOPPER_RALLY_MISSION_ONE_AUTHORING_V1: MissionAuthoringBundleV1 = {\n version: MISSION_AUTHORING_CONTRACT_VERSION_V1,\n moduleId: \"junior-coder.road-hopper-rally\",\n moduleVersion: \"1.1.0\",\n missionId: \"road-hopper-rally-mission-1\",\n learner: {\n estimatedMinutes: 20,\n stages: [\n {\n kind: \"learn\",\n instruction: \"Find the x and y coordinates that place a rescue marker on the road.\",\n artifactIds: [\"road-hopper-rally-m1-art\"],\n },\n {\n kind: \"predict\",\n instruction: \"Predict where the marker will appear before you run the starter project.\",\n artifactIds: [],\n },\n {\n kind: \"build\",\n instruction: \"Complete the two road-lane drawing commands in the starter code.\",\n artifactIds: [\"road-hopper-rally-m1-code\"],\n },\n {\n kind: \"run\",\n instruction: \"Run the private preview and use the keyboard start control.\",\n artifactIds: [\"road-hopper-rally-m1-code\"],\n },\n {\n kind: \"assess\",\n instruction: \"Run the visible and protected deterministic mission checks.\",\n artifactIds: [],\n },\n {\n kind: \"inspect\",\n instruction: \"Compare the highlighted line with the goal that did not pass.\",\n artifactIds: [],\n },\n {\n kind: \"fix\",\n instruction: \"Change one coordinate or drawing command, then run the checks again.\",\n artifactIds: [\"road-hopper-rally-m1-code\"],\n },\n {\n kind: \"explain\",\n instruction: \"Explain how your coordinates changed the road on screen.\",\n artifactIds: [],\n },\n {\n kind: \"reward\",\n instruction: \"Collect the evidence-bound badge when the mission score and safety check pass.\",\n artifactIds: [],\n },\n ],\n readinessChecks: [\n {\n id: \"road-hopper-rally-m1-predict-coordinate\",\n prompt: \"Point to the pair of numbers that controls horizontal and vertical position.\",\n scored: false,\n },\n ],\n artifacts: [\n {\n id: \"road-hopper-rally-m1-code\",\n kind: \"starter-code\",\n audience: \"learner\",\n solutionBearing: false,\n },\n {\n id: \"road-hopper-rally-m1-art\",\n kind: \"starter-assets\",\n audience: \"learner\",\n solutionBearing: false,\n },\n ],\n goals: [\n {\n id: \"road-hopper-rally-m1-starts\",\n statement: \"The starter project is structurally valid and starts.\",\n visibility: \"visible\",\n criterionIds: [\"road-hopper-rally-build\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"road-hopper-rally-m1-draws-road\",\n statement: \"Two original road lanes appear at the expected coordinates.\",\n visibility: \"visible\",\n criterionIds: [\"road-hopper-rally-goal-one\"],\n completionRequired: true,\n aiRequired: false,\n },\n {\n id: \"road-hopper-rally-m1-safe-preview\",\n statement: \"The project stays inside the private educational preview boundary.\",\n visibility: \"visible\",\n criterionIds: [\"road-hopper-rally-safety\"],\n completionRequired: true,\n aiRequired: false,\n },\n ],\n interactions: [\n {\n id: \"road-hopper-rally-m1-start-control\",\n description: \"Start the private preview.\",\n primaryMode: \"pointer\",\n alternativeIds: [\"road-hopper-rally-m1-keyboard-start\"],\n },\n ],\n accessibilityAlternatives: [\n {\n id: \"road-hopper-rally-m1-keyboard-start\",\n modes: [\"keyboard\"],\n equivalentOutcome: true,\n description: \"Start the same preview with Enter while the control has focus.\",\n },\n ],\n evidenceRequirements: [\n {\n id: \"road-hopper-rally-m1-assessment\",\n goalIds: [\n \"road-hopper-rally-m1-starts\",\n \"road-hopper-rally-m1-draws-road\",\n \"road-hopper-rally-m1-safe-preview\",\n ],\n kind: \"assessment-result\",\n retention: \"entitlement\",\n containsPersonalData: false,\n },\n {\n id: \"road-hopper-rally-m1-explanation\",\n goalIds: [\"road-hopper-rally-m1-draws-road\"],\n kind: \"learner-explanation\",\n retention: \"attempt\",\n containsPersonalData: false,\n },\n ],\n sideAdventures: [\n {\n id: \"road-hopper-rally-m1-remix\",\n prompt: \"Remix the lane colours while keeping text or shape cues available.\",\n completionRequired: false,\n },\n ],\n rewardBindings: [\n {\n id: \"road-hopper-rally-m1-badge\",\n badgeId: \"road-hopper-rally-mission-complete\",\n goalIds: [\n \"road-hopper-rally-m1-starts\",\n \"road-hopper-rally-m1-draws-road\",\n \"road-hopper-rally-m1-safe-preview\",\n ],\n deterministic: true,\n random: false,\n tokenConvertible: false,\n },\n ],\n },\n facilitator: {\n artifacts: [\n {\n id: \"road-hopper-rally-m1-answer-key\",\n kind: \"answer-key\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n {\n id: \"road-hopper-rally-m1-protected-tests\",\n kind: \"protected-test\",\n audience: \"facilitator\",\n solutionBearing: true,\n },\n ],\n protectedGoals: [\n {\n id: \"road-hopper-rally-m1-protected-edge\",\n statement: \"The drawing remains bounded when a protected coordinate edge case runs.\",\n visibility: \"protected\",\n criterionIds: [\"road-hopper-rally-edge-one\"],\n completionRequired: false,\n aiRequired: false,\n },\n ],\n prompts: [\n \"Ask the learner to predict one coordinate before offering a hint.\",\n \"Do not reveal protected expected values; point back to the visible goal.\",\n ],\n },\n};\n","import type { MissionStageKindV1, ModuleCategoryV1 } from \"./contracts.js\";\nimport { JUNIOR_CODER_MISSION_STAGE_ORDER_V1 } from \"./mission-authoring.js\";\n\n/** Stable activity order for complete courses; legacy authoring remains immutable. */\nexport const LEARNING_COURSE_STAGE_ORDER = JUNIOR_CODER_MISSION_STAGE_ORDER_V1;\nexport const LEARNING_COURSE_LIMITS = Object.freeze({\n missions: 6, stagesPerMission: 9, projectFiles: 8,\n sourceCharactersPerFile: 64_000, sourceCharactersPerProject: 96_000,\n});\n\nexport type LearningProjectLanguageV1 = \"javascript\" | \"python\" | \"cpp\" | \"html\" | \"css\" | \"blocks\" | \"json\";\nexport interface LearningProjectFileV1 { path: string; source: string }\nexport interface LearningProjectV1 { files: LearningProjectFileV1[] }\nexport interface LearningCourseStageV1 {\n id: string;\n kind: MissionStageKindV1;\n title: string;\n instruction: string;\n help: string;\n}\nexport interface LearningCourseMissionV1 {\n id: string;\n title: string;\n concepts: string[];\n estimatedMinutes: number;\n goals: string[];\n assessmentId: string;\n stages: LearningCourseStageV1[];\n extension: string;\n}\n\n/** Learner-safe content only. Protected scenarios and solutions are host-owned. */\nexport interface LearningCourseV1 {\n schemaVersion: \"1\";\n moduleId: string;\n moduleVersion: string;\n slug: string;\n title: string;\n summary: string;\n runtimeId: string;\n category: ModuleCategoryV1;\n estimatedMinutes: number;\n completionAssessmentId: string;\n projectFiles: { path: string; language: LearningProjectLanguageV1; maximumCharacters: number }[];\n starterProject: LearningProjectV1;\n reference: { name: string; signature: string; description: string; example: string }[];\n missions: LearningCourseMissionV1[];\n completionBadge: { id: string; title: string };\n}\n\n/** The only learner-owned save inputs. Progress/evidence are never draft authority. */\nexport interface LearningCourseDraftV1 {\n schemaVersion: \"1\";\n moduleVersion: string;\n activeStageId: string;\n project: LearningProjectV1;\n}\nexport type LearningSaveSlotIdV1 = \"auto\" | \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"7\" | \"8\" | \"9\";\nexport interface LearningCourseValidationIssueV1 {\n code: \"invalid-manifest\" | \"mission-count\" | \"stage-order\" | \"duplicate-id\" | \"incomplete-content\" | \"invalid-project\";\n path: string;\n}\n\nconst ID = /^[a-z0-9][a-z0-9.-]{0,159}$/u;\nconst FILE_PATH = /^[a-z0-9][a-z0-9_-]{0,63}\\.(?:js|py|cpp|html|css|json)$/u;\nconst VERSION = /^\\d+\\.\\d+\\.\\d+$/u;\nconst LANGUAGES: readonly string[] = [\"javascript\", \"python\", \"cpp\", \"html\", \"css\", \"blocks\", \"json\"];\nconst CATEGORIES: readonly string[] = [\"game\", \"robot\", \"vibe\", \"web-app\"];\nconst PLACEHOLDER = /\\b(?:TODO|TBD|coming soon|placeholder|lorem ipsum)\\b/iu;\nconst record = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\nconst text = (value: unknown, minimum = 1, maximum = 8000): value is string =>\n typeof value === \"string\" && value.trim().length >= minimum && value.length <= maximum;\nconst id = (value: unknown): value is string => typeof value === \"string\" && ID.test(value);\nconst integer = (value: unknown, minimum: number, maximum: number): value is number =>\n typeof value === \"number\" && Number.isSafeInteger(value) && value >= minimum && value <= maximum;\nconst exactKeys = (value: Record<string, unknown>, keys: readonly string[]): boolean =>\n Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));\n\n/** Errors contain no source, learner text or caller-supplied identifiers. */\nexport class LearningCourseInputError extends Error {\n constructor() { super(\"Invalid learning course input.\"); this.name = \"LearningCourseInputError\"; }\n}\n\nfunction fileDefinitions(value: unknown): value is LearningCourseV1[\"projectFiles\"] {\n return Array.isArray(value) && value.length >= 1 && value.length <= LEARNING_COURSE_LIMITS.projectFiles\n && value.every(file => record(file) && exactKeys(file, [\"path\", \"language\", \"maximumCharacters\"])\n && typeof file.path === \"string\" && FILE_PATH.test(file.path)\n && typeof file.language === \"string\" && LANGUAGES.includes(file.language)\n && integer(file.maximumCharacters, 1, LEARNING_COURSE_LIMITS.sourceCharactersPerFile))\n && new Set(value.map(file => (file as { path: string }).path)).size === value.length;\n}\n\n/** Validate every file against the declared editable set; never preserve extra fields. */\nexport function parseLearningProject(course: Pick<LearningCourseV1, \"projectFiles\">, value: unknown): LearningProjectV1 {\n if (!fileDefinitions(course.projectFiles) || !record(value) || !exactKeys(value, [\"files\"])\n || !Array.isArray(value.files) || value.files.length !== course.projectFiles.length) throw new LearningCourseInputError();\n const files: LearningProjectFileV1[] = [];\n const seen = new Set<string>();\n let characters = 0;\n for (const file of value.files) {\n if (!record(file) || !exactKeys(file, [\"path\", \"source\"]) || typeof file.path !== \"string\"\n || typeof file.source !== \"string\" || seen.has(file.path)) throw new LearningCourseInputError();\n const definition = course.projectFiles.find(candidate => candidate.path === file.path);\n if (!definition || file.source.length > definition.maximumCharacters || file.source.includes(\"\\u0000\")) throw new LearningCourseInputError();\n characters += file.source.length;\n if (characters > LEARNING_COURSE_LIMITS.sourceCharactersPerProject) throw new LearningCourseInputError();\n seen.add(file.path);\n files.push({ path: file.path, source: file.source });\n }\n // Stable ordering keeps host digests independent of the submitted array order.\n return { files: course.projectFiles.map(definition => files.find(file => file.path === definition.path)!) };\n}\n\n/** Parse untrusted saves without accepting identity, scores, completion or evidence. */\nexport function parseLearningCourseDraft(course: LearningCourseV1, value: unknown): LearningCourseDraftV1 {\n if (!record(value) || !exactKeys(value, [\"schemaVersion\", \"moduleVersion\", \"activeStageId\", \"project\"])\n || value.schemaVersion !== \"1\" || value.moduleVersion !== course.moduleVersion\n || typeof value.activeStageId !== \"string\"\n || !course.missions.some(mission => mission.stages.some(stage => stage.id === value.activeStageId))) throw new LearningCourseInputError();\n return { schemaVersion: \"1\", moduleVersion: course.moduleVersion, activeStageId: value.activeStageId,\n project: parseLearningProject(course, value.project) };\n}\n\n/** An account owns one autosave and nine explicitly managed slots per course version. */\nexport function parseLearningSaveSlotId(value: unknown): LearningSaveSlotIdV1 {\n if (value === \"auto\" || (typeof value === \"string\" && /^[1-9]$/u.test(value))) return value as LearningSaveSlotIdV1;\n throw new LearningCourseInputError();\n}\n\n/** Structural publication gate. Working runtime/curriculum acceptance is additional. */\nexport function validateLearningCourse(value: unknown): LearningCourseValidationIssueV1[] {\n const issues: LearningCourseValidationIssueV1[] = [];\n const add = (code: LearningCourseValidationIssueV1[\"code\"], path: string) => { issues.push({ code, path }); };\n if (!record(value)) return [{ code: \"invalid-manifest\", path: \"$\" }];\n if (!exactKeys(value, [\"schemaVersion\", \"moduleId\", \"moduleVersion\", \"slug\", \"title\", \"summary\", \"runtimeId\", \"category\",\n \"estimatedMinutes\", \"completionAssessmentId\", \"projectFiles\", \"starterProject\", \"reference\", \"missions\", \"completionBadge\"])\n || value.schemaVersion !== \"1\" || !id(value.moduleId) || !id(value.slug) || !id(value.runtimeId)\n || !text(value.moduleVersion) || !VERSION.test(value.moduleVersion)\n || typeof value.category !== \"string\" || !CATEGORIES.includes(value.category)\n || !text(value.title, 3, 160) || !text(value.summary, 40, 2000)\n || !integer(value.estimatedMinutes, 60, 3600) || !id(value.completionAssessmentId)\n || !record(value.completionBadge) || !exactKeys(value.completionBadge, [\"id\", \"title\"])\n || !id(value.completionBadge.id) || !text(value.completionBadge.title, 3, 160)) add(\"invalid-manifest\", \"$\");\n if (!fileDefinitions(value.projectFiles)) add(\"invalid-project\", \"projectFiles\");\n else {\n try { parseLearningProject({ projectFiles: value.projectFiles }, value.starterProject); }\n catch { add(\"invalid-project\", \"starterProject\"); }\n }\n if (!Array.isArray(value.reference) || value.reference.length < 1 || value.reference.length > 80\n || value.reference.some(entry => !record(entry) || !exactKeys(entry, [\"name\", \"signature\", \"description\", \"example\"])\n || !text(entry.name) || !text(entry.signature)\n || !text(entry.description, 20) || !text(entry.example))) add(\"incomplete-content\", \"reference\");\n if (!Array.isArray(value.missions)) { add(\"mission-count\", \"missions\"); return issues; }\n if (value.missions.length !== LEARNING_COURSE_LIMITS.missions) add(\"mission-count\", \"missions\");\n if (value.missions.length > LEARNING_COURSE_LIMITS.missions) return issues;\n const seen = new Set<string>();\n const checkId = (candidate: unknown, path: string) => {\n if (!id(candidate)) add(\"invalid-manifest\", path);\n else if (seen.has(candidate)) add(\"duplicate-id\", path);\n else seen.add(candidate);\n };\n let minutes = 0;\n value.missions.forEach((mission: unknown, missionIndex: number) => {\n const path = `missions[${missionIndex}]`;\n if (!record(mission)) { add(\"invalid-manifest\", path); return; }\n checkId(mission.id, `${path}.id`);\n checkId(mission.assessmentId, `${path}.assessmentId`);\n if (!exactKeys(mission, [\"id\", \"title\", \"concepts\", \"estimatedMinutes\", \"goals\", \"assessmentId\", \"stages\", \"extension\"])\n || !text(mission.title, 3, 160) || !integer(mission.estimatedMinutes, 10, 600)\n || !Array.isArray(mission.concepts) || mission.concepts.length < 1 || mission.concepts.length > 12\n || mission.concepts.some(concept => !text(concept, 2, 120))\n || !Array.isArray(mission.goals) || mission.goals.length < 1 || mission.goals.length > 12\n || mission.goals.some(goal => !text(goal, 20, 2000)) || !text(mission.extension, 30, 2000)) add(\"incomplete-content\", path);\n if (typeof mission.estimatedMinutes === \"number\") minutes += mission.estimatedMinutes;\n if (!Array.isArray(mission.stages) || mission.stages.length !== LEARNING_COURSE_LIMITS.stagesPerMission) {\n add(\"stage-order\", `${path}.stages`); return;\n }\n mission.stages.forEach((stage: unknown, stageIndex: number) => {\n const stagePath = `${path}.stages[${stageIndex}]`;\n if (!record(stage)) { add(\"invalid-manifest\", stagePath); return; }\n checkId(stage.id, `${stagePath}.id`);\n if (stage.kind !== LEARNING_COURSE_STAGE_ORDER[stageIndex]) add(\"stage-order\", stagePath);\n if (!exactKeys(stage, [\"id\", \"kind\", \"title\", \"instruction\", \"help\"])\n || !text(stage.title, 3, 160) || !text(stage.instruction, 40) || !text(stage.help, 30)\n || (typeof stage.instruction === \"string\" && PLACEHOLDER.test(stage.instruction))) add(\"incomplete-content\", stagePath);\n });\n });\n if (minutes !== value.estimatedMinutes) add(\"invalid-manifest\", \"estimatedMinutes\");\n return issues;\n}\n\n/** Validate an external learner manifest and detach it from the supplied object. */\nexport function parseLearningCourse(value: unknown): LearningCourseV1 {\n if (validateLearningCourse(value).length) throw new LearningCourseInputError();\n return structuredClone(value as LearningCourseV1);\n}\n","import { LEARNING_COURSE_STAGE_ORDER, parseLearningCourse, type LearningCourseV1 } from \"../course-contracts.js\";\nimport type { MissionStageKindV1 } from \"../contracts.js\";\n\n/** Formative checks are learner-visible teaching material, not protected assessments. */\nexport interface CoursePracticeQuestion {\n stageId: string;\n question: string;\n choices: [string, string, string];\n correctChoice: 0 | 1 | 2;\n feedback: string;\n}\nexport type PracticeDraft = Omit<CoursePracticeQuestion, \"stageId\">;\nexport interface CourseMissionDraft {\n title: string;\n concepts: string[];\n goals: string[];\n extension: string;\n activities: Record<MissionStageKindV1, [instruction: string, help: string]>;\n questions: Record<\"learn\" | \"predict\" | \"explain\", PracticeDraft>;\n}\ntype CourseHeader = Omit<LearningCourseV1, \"schemaVersion\" | \"moduleVersion\" | \"moduleId\" | \"runtimeId\" | \"estimatedMinutes\" | \"completionAssessmentId\" | \"completionBadge\" | \"missions\">;\n\n/** Only identifiers and activity framing are shared; each lesson is independently authored. */\nexport function authorCourse(header: CourseHeader, missions: CourseMissionDraft[]): { course: LearningCourseV1; practice: CoursePracticeQuestion[] } {\n const course = parseLearningCourse({ ...header, schemaVersion: \"1\", moduleVersion: \"2.0.0\", moduleId: `junior-coder.${header.slug}`,\n runtimeId: `${header.slug}.v2`, estimatedMinutes: missions.length * 60,\n completionAssessmentId: `${header.slug}.final`, completionBadge: { id: `${header.slug}.completed`, title: `${header.title} creator` },\n missions: missions.map((mission, index) => ({\n id: `${header.slug}.m${index + 1}`, title: mission.title, concepts: mission.concepts, goals: mission.goals,\n estimatedMinutes: 60, assessmentId: `${header.slug}.m${index + 1}.assessment`, extension: mission.extension,\n stages: LEARNING_COURSE_STAGE_ORDER.map(kind => ({ id: `${header.slug}.m${index + 1}.${kind}`, kind,\n title: `${kind.charAt(0).toUpperCase()}${kind.slice(1)}: ${mission.title}`,\n instruction: mission.activities[kind][0], help: mission.activities[kind][1] })),\n })),\n });\n const practice = structuredClone(missions.flatMap((mission, index) => ([\"learn\", \"predict\", \"explain\"] as const)\n .map(kind => ({ ...mission.questions[kind], stageId: `${header.slug}.m${index + 1}.${kind}` }))));\n for (const question of practice) {\n if (question.question.trim().length < 20 || question.feedback.trim().length < 40\n || question.choices.length !== 3 || question.choices.some(choice => choice.trim().length < 2) || new Set(question.choices).size !== 3\n || !Number.isInteger(question.correctChoice) || question.correctChoice < 0 || question.correctChoice > 2) throw new Error(\"Invalid course practice question.\");\n }\n return { course, practice };\n}\n","import type { LearningCourseV1 } from \"../course-contracts.js\";\n\n/** The web courses share file limits and a readable starting theme, not lesson content. */\nexport const webProjectFiles: LearningCourseV1[\"projectFiles\"] = [\n { path: \"index.html\", language: \"html\", maximumCharacters: 24000 },\n { path: \"app.css\", language: \"css\", maximumCharacters: 16000 },\n { path: \"app.js\", language: \"javascript\", maximumCharacters: 32000 },\n];\n\nexport const webStarterCss = `:root { color: #172a29; background-color: #f5f4ec; font-family: system-ui, sans-serif; line-height: 1.6; }\n* { box-sizing: border-box; }\nmain { max-width: 60rem; margin-inline: auto; padding: 1rem; overflow-wrap: anywhere; }\nh1, h2, p { margin-top: 0; }\nsection, article, fieldset { border: 1px solid #647874; border-radius: 0.5rem; padding: 1rem; margin-block: 1rem; min-width: 0; }\nlabel { display: block; font-weight: 700; }\nbutton, input, select, textarea { font-family: inherit; font-size: 1rem; line-height: 1.6; min-height: 2.75rem; max-width: 100%; border: 2px solid #465e59; border-radius: 0.25rem; padding: 0.5rem; color: inherit; background-color: #ffffff; }\nbutton { cursor: pointer; }\nbutton:disabled { cursor: default; border-style: dashed; }\n:focus-visible { outline: 3px solid #075bab; outline-offset: 3px; }\n[aria-invalid=\"true\"] { border-color: #a11c25; }\n.error { color: #a11c25; }\n.actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }\n.cards { display: grid; gap: 1rem; padding: 0; list-style-type: none; }\nprogress, meter { width: 100%; }\n@media (min-width: 48rem) { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }\n@media (prefers-reduced-motion: reduce) { * { transition-duration: 0s; } }\n@media (prefers-color-scheme: dark) {\n :root { color: #edf4ef; background-color: #142623; }\n button, input, select, textarea { color: #edf4ef; background-color: #203a34; border-color: #b8cec5; }\n :focus-visible { outline-color: #a8d6ff; }\n .error { color: #ffb8bf; }\n [aria-invalid=\"true\"] { border-color: #ffb8bf; }\n}\n`;\n\nexport const webReferences: LearningCourseV1[\"reference\"] = [\n { name: \"editable web files\", signature: \"index.html + app.css + app.js\", description: \"Write a semantic HTML fragment beginning with main, scoped responsive CSS and pure JavaScript initialState/update/view functions. The host compiles HTML/CSS and runs JavaScript separately. No document, DOM, imports, network, localStorage, timers, images or external resources are available. Fictional project data stays inside the preview; course source is saved through the bound account.\", example: '<main><h1>My project</h1><p role=\"status\" data-text=\"message\"></p></main>' },\n { name: \"bindings\", signature: \"view(state) → plain JSON display data\", description: \"data-text displays scalar text; data-value controls a native field; data-checked, data-disabled, data-pressed, data-invalid and data-if require booleans. data-label supplies a nonempty accessible label. data-repeat repeats its element for records with unique string id, resolving nested bindings within each record. Do not put static HTML IDs in repeated content. Paths use named fields up to four segments, without expressions.\", example: '<li data-repeat=\"missions\"><span data-text=\"title\"></span><button type=\"button\" data-action=\"toggle\" data-id=\"id\" data-pressed=\"done\" data-label=\"toggleLabel\">Toggle</button></li>' },\n { name: \"native events\", signature: '{ type: \"field\", name, value } or { type, id? }', description: \"A named input, textarea or select emits field with its string value (checkboxes use booleans). A type=button with data-action emits that action and optional bound data-id. A form with data-action owns submission; its submit button has no separate action. Prevent network submission. Preserve native labels, focus and keyboard operation; input text is never executable markup.\", example: '<form data-action=\"add\" novalidate><label for=\"title\">Mission</label><input id=\"title\" name=\"title\" data-value=\"draft.title\"><button type=\"submit\">Add mission</button></form>' },\n];\n"],"mappings":"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","names":["course","course","practice"]}
|