feat: overlay manifests for the two reference courses

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-30 02:52:24 +01:00
co-authored by Claude Opus 5.5
parent c78c7ec8d8
commit b8c65626f9
3 changed files with 700 additions and 0 deletions
+558
View File
@@ -0,0 +1,558 @@
schema: 1
id: pure-snake
title: Pure Snake
pitch: Build Snake as pure functions, then wire it to the world with IO and event streams, in JavaScript, Scala 3 or F#.
visibility: public
root: /home/user/functional-programming-course
tracks:
- { id: js, label: JavaScript }
- { id: scala, label: Scala 3 }
- { id: fsharp, label: F# }
mentor: MENTOR.md
source: { kind: authored }
modules:
- id: m00
title: "Setup and how to use this course"
lessons:
- { id: m00-text, title: "Setup and how to use this course", type: text, file: "course/00-setup.md" }
- id: m00-ex-green-lights
title: "Exercise 0.1 — Green lights"
type: exercise
file: "course/00-setup.md"
anchor: exercise-01--green-lights
hints: { file: "course/00-setup.md", match: "Exercise 0.1" }
localCommand:
js: "cd js && node --test \"test/m00-*.test.js\""
scala: "cd scala && ./test.sh M00"
fsharp: "cd fsharp && ./test.sh M00"
- id: m00-ex-read-a-red-test
title: "Exercise 0.2 — Read a red test"
type: exercise
file: "course/00-setup.md"
anchor: exercise-02--read-a-red-test
hints: { file: "course/00-setup.md", match: "Exercise 0.2" }
localCommand:
js: "cd js && node --test \"test/m00-*.test.js\""
scala: "cd scala && ./test.sh M00"
fsharp: "cd fsharp && ./test.sh M00"
- id: m00-ex-break-the-purity-audit-on-purpose
title: "Exercise 0.3 — Break the purity audit on purpose"
type: exercise
file: "course/00-setup.md"
anchor: exercise-03--break-the-purity-audit-on-purpose
localCommand:
js: "cd js && node --test \"test/m00-*.test.js\""
scala: "cd scala && ./test.sh M00"
fsharp: "cd fsharp && ./test.sh M00"
- id: m01
title: "Purity and immutability"
lessons:
- { id: m01-text, title: "Purity and immutability", type: text, file: "course/01-purity-and-immutability.md" }
- id: m01-ex-range
title: "Exercise 1.1 — `range`"
type: exercise
file: "course/01-purity-and-immutability.md"
anchor: exercise-11--range
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.1 (range)" }
localCommand:
js: "cd js && node --test \"test/m01-*.test.js\""
scala: "cd scala && ./test.sh M01"
fsharp: "cd fsharp && ./test.sh M01"
- id: m01-ex-droplast
title: "Exercise 1.2 — `dropLast`"
type: exercise
file: "course/01-purity-and-immutability.md"
anchor: exercise-12--droplast
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.2 (dropLast)" }
localCommand:
js: "cd js && node --test \"test/m01-*.test.js\""
scala: "cd scala && ./test.sh M01"
fsharp: "cd fsharp && ./test.sh M01"
- id: m01-ex-contains
title: "Exercise 1.3 — `contains`"
type: exercise
file: "course/01-purity-and-immutability.md"
anchor: exercise-13--contains
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.3 (contains)" }
localCommand:
js: "cd js && node --test \"test/m01-*.test.js\""
scala: "cd scala && ./test.sh M01"
fsharp: "cd fsharp && ./test.sh M01"
- id: m01-ex-pipe
title: "Exercise 1.4 — `pipe` (JS only)"
type: exercise
file: "course/01-purity-and-immutability.md"
anchor: exercise-14--pipe-js-only
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.4 (pipe)" }
localCommand:
js: "cd js && node --test \"test/m01-*.test.js\""
scala: "cd scala && ./test.sh M01"
fsharp: "cd fsharp && ./test.sh M01"
- id: m01-ex-compose
title: "Exercise 1.5 — `compose` (JS only)"
type: exercise
file: "course/01-purity-and-immutability.md"
anchor: exercise-15--compose-js-only
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.5 (compose)" }
localCommand:
js: "cd js && node --test \"test/m01-*.test.js\""
scala: "cd scala && ./test.sh M01"
fsharp: "cd fsharp && ./test.sh M01"
- id: m01-ex-deepfreeze
title: "Exercise 1.6 — `deepFreeze` (JS only)"
type: exercise
file: "course/01-purity-and-immutability.md"
anchor: exercise-16--deepfreeze-js-only
hints: { file: "course/01-purity-and-immutability.md", match: "Exercise 1.6 (deepFreeze)" }
localCommand:
js: "cd js && node --test \"test/m01-*.test.js\""
scala: "cd scala && ./test.sh M01"
fsharp: "cd fsharp && ./test.sh M01"
- id: m02
title: "The game as data"
lessons:
- { id: m02-text, title: "The game as data", type: text, file: "course/02-the-game-as-data.md" }
- id: m02-ex-your-own-design
title: "Exercise 2.0 — Your own design (paper)"
type: exercise
file: "course/02-the-game-as-data.md"
anchor: exercise-20--your-own-design-paper
localCommand:
js: "cd js && node --test \"test/m02-*.test.js\""
scala: "cd scala && ./test.sh M02"
fsharp: "cd fsharp && ./test.sh M02"
- id: m02-ex-point
title: "Exercise 2.1 — `point` and `pointEquals` (JS)"
type: exercise
file: "course/02-the-game-as-data.md"
anchor: exercise-21--point-and-pointequals-js
hints: { file: "course/02-the-game-as-data.md", match: "Exercise 2.1 (pointEquals)" }
localCommand:
js: "cd js && node --test \"test/m02-*.test.js\""
scala: "cd scala && ./test.sh M02"
fsharp: "cd fsharp && ./test.sh M02"
- id: m02-ex-delta
title: "Exercise 2.2 — `delta`"
type: exercise
file: "course/02-the-game-as-data.md"
anchor: exercise-22--delta
hints: { file: "course/02-the-game-as-data.md", match: "Exercises 2.2 and 2.3 (delta, opposite)" }
localCommand:
js: "cd js && node --test \"test/m02-*.test.js\""
scala: "cd scala && ./test.sh M02"
fsharp: "cd fsharp && ./test.sh M02"
- id: m02-ex-opposite
title: "Exercise 2.3 — `opposite`"
type: exercise
file: "course/02-the-game-as-data.md"
anchor: exercise-23--opposite
hints: { file: "course/02-the-game-as-data.md", match: "Exercises 2.2 and 2.3 (delta, opposite)" }
localCommand:
js: "cd js && node --test \"test/m02-*.test.js\""
scala: "cd scala && ./test.sh M02"
fsharp: "cd fsharp && ./test.sh M02"
- id: m02-ex-action
title: "Exercise 2.4 — `Action` (JS)"
type: exercise
file: "course/02-the-game-as-data.md"
anchor: exercise-24--action-js
hints: { file: "course/02-the-game-as-data.md", match: "Exercise 2.4 (Action)" }
localCommand:
js: "cd js && node --test \"test/m02-*.test.js\""
scala: "cd scala && ./test.sh M02"
fsharp: "cd fsharp && ./test.sh M02"
- id: m02-ex-initialstate
title: "Exercise 2.5 — `initialState`"
type: exercise
file: "course/02-the-game-as-data.md"
anchor: exercise-25--initialstate
hints: { file: "course/02-the-game-as-data.md", match: "Exercise 2.5 (initialState)" }
localCommand:
js: "cd js && node --test \"test/m02-*.test.js\""
scala: "cd scala && ./test.sh M02"
fsharp: "cd fsharp && ./test.sh M02"
- id: m03
title: "Pure game rules"
lessons:
- { id: m03-text, title: "Pure game rules", type: text, file: "course/03-pure-game-rules.md" }
- id: m03-ex-move
title: "Exercise 3.1 — `move`"
type: exercise
file: "course/03-pure-game-rules.md"
anchor: exercise-31--move
hints: { file: "course/03-pure-game-rules.md", match: "Exercise 3.1 (move)" }
localCommand:
js: "cd js && node --test \"test/m03-*.test.js\""
scala: "cd scala && ./test.sh M03"
fsharp: "cd fsharp && ./test.sh M03"
- id: m03-ex-inbounds
title: "Exercise 3.2 — `inBounds`"
type: exercise
file: "course/03-pure-game-rules.md"
anchor: exercise-32--inbounds
hints: { file: "course/03-pure-game-rules.md", match: "Exercise 3.2 (inBounds)" }
localCommand:
js: "cd js && node --test \"test/m03-*.test.js\""
scala: "cd scala && ./test.sh M03"
fsharp: "cd fsharp && ./test.sh M03"
- id: m03-ex-turn
title: "Exercise 3.3 — `turn`"
type: exercise
file: "course/03-pure-game-rules.md"
anchor: exercise-33--turn
hints: { file: "course/03-pure-game-rules.md", match: "Exercise 3.3 (turn)" }
localCommand:
js: "cd js && node --test \"test/m03-*.test.js\""
scala: "cd scala && ./test.sh M03"
fsharp: "cd fsharp && ./test.sh M03"
- id: m03-ex-advance
title: "Exercise 3.4 — `advance`"
type: exercise
file: "course/03-pure-game-rules.md"
anchor: exercise-34--advance
hints: { file: "course/03-pure-game-rules.md", match: "Exercise 3.4 (advance)" }
localCommand:
js: "cd js && node --test \"test/m03-*.test.js\""
scala: "cd scala && ./test.sh M03"
fsharp: "cd fsharp && ./test.sh M03"
- id: m04
title: "Randomness without side effects"
lessons:
- { id: m04-text, title: "Randomness without side effects", type: text, file: "course/04-randomness-without-side-effects.md" }
- id: m04-ex-nextuint32
title: "Exercise 4.1 — `nextUint32`"
type: exercise
file: "course/04-randomness-without-side-effects.md"
anchor: exercise-41--nextuint32
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.1 (nextUint32)" }
localCommand:
js: "cd js && node --test \"test/m04-*.test.js\""
scala: "cd scala && ./test.sh M04"
fsharp: "cd fsharp && ./test.sh M04"
- id: m04-ex-pure
title: "Exercise 4.2 — `pure`, `map`, `flatMap`, `run`"
type: exercise
file: "course/04-randomness-without-side-effects.md"
anchor: exercise-42--pure-map-flatmap-run
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.2 (pure, map, flatMap, run)" }
localCommand:
js: "cd js && node --test \"test/m04-*.test.js\""
scala: "cd scala && ./test.sh M04"
fsharp: "cd fsharp && ./test.sh M04"
- id: m04-ex-intbelow
title: "Exercise 4.3 — `intBelow`"
type: exercise
file: "course/04-randomness-without-side-effects.md"
anchor: exercise-43--intbelow
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.3 (intBelow)" }
localCommand:
js: "cd js && node --test \"test/m04-*.test.js\""
scala: "cd scala && ./test.sh M04"
fsharp: "cd fsharp && ./test.sh M04"
- id: m04-ex-freecells
title: "Exercise 4.4 — `freeCells`"
type: exercise
file: "course/04-randomness-without-side-effects.md"
anchor: exercise-44--freecells
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.4 (freeCells)" }
localCommand:
js: "cd js && node --test \"test/m04-*.test.js\""
scala: "cd scala && ./test.sh M04"
fsharp: "cd fsharp && ./test.sh M04"
- id: m04-ex-spawnapple
title: "Exercise 4.5 — `spawnApple`"
type: exercise
file: "course/04-randomness-without-side-effects.md"
anchor: exercise-45--spawnapple
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.5 (spawnApple)" }
localCommand:
js: "cd js && node --test \"test/m04-*.test.js\""
scala: "cd scala && ./test.sh M04"
fsharp: "cd fsharp && ./test.sh M04"
- id: m04-ex-tick
title: "Exercise 4.6 — `tick` and `newGame`"
type: exercise
file: "course/04-randomness-without-side-effects.md"
anchor: exercise-46--tick-and-newgame
hints: { file: "course/04-randomness-without-side-effects.md", match: "Exercise 4.6 (tick, newGame)" }
localCommand:
js: "cd js && node --test \"test/m04-*.test.js\""
scala: "cd scala && ./test.sh M04"
fsharp: "cd fsharp && ./test.sh M04"
- id: m05
title: "Folding time"
lessons:
- { id: m05-text, title: "Folding time", type: text, file: "course/05-folding-time.md" }
- id: m05-ex-update
title: "Exercise 5.1 — `update`"
type: exercise
file: "course/05-folding-time.md"
anchor: exercise-51--update
hints: { file: "course/05-folding-time.md", match: "Exercise 5.1 (update)" }
localCommand:
js: "cd js && node --test \"test/m05-*.test.js\""
scala: "cd scala && ./test.sh M05"
fsharp: "cd fsharp && ./test.sh M05"
- id: m05-ex-replay
title: "Exercise 5.2 — `replay`"
type: exercise
file: "course/05-folding-time.md"
anchor: exercise-52--replay
hints: { file: "course/05-folding-time.md", match: "Exercise 5.2 (replay)" }
localCommand:
js: "cd js && node --test \"test/m05-*.test.js\""
scala: "cd scala && ./test.sh M05"
fsharp: "cd fsharp && ./test.sh M05"
- id: m05-ex-history
title: "Exercise 5.3 — `history`"
type: exercise
file: "course/05-folding-time.md"
anchor: exercise-53--history
hints: { file: "course/05-folding-time.md", match: "Exercise 5.3 (history)" }
localCommand:
js: "cd js && node --test \"test/m05-*.test.js\""
scala: "cd scala && ./test.sh M05"
fsharp: "cd fsharp && ./test.sh M05"
- id: m06
title: "Rendering as data"
lessons:
- { id: m06-text, title: "Rendering as data", type: text, file: "course/06-rendering-as-data.md" }
- id: m06-ex-view
title: "Exercise 6.1 — `view`"
type: exercise
file: "course/06-rendering-as-data.md"
anchor: exercise-61--view
hints: { file: "course/06-rendering-as-data.md", match: "Exercise 6.1" }
localCommand:
js: "cd js && node --test \"test/m06-*.test.js\""
scala: "cd scala && ./test.sh M06"
fsharp: "cd fsharp && ./test.sh M06"
- id: m06-ex-a-second-interpreter
title: "Exercise 6.2 (optional, untested) — a second interpreter"
type: exercise
file: "course/06-rendering-as-data.md"
anchor: exercise-62-optional-untested--a-second-interpreter
localCommand:
js: "cd js && node --test \"test/m06-*.test.js\""
scala: "cd scala && ./test.sh M06"
fsharp: "cd fsharp && ./test.sh M06"
- id: m07
title: "The IO type"
lessons:
- { id: m07-text, title: "The IO type", type: text, file: "course/07-the-io-type.md" }
- id: m07-ex-pure
title: "Exercise 7.1 — `pure` and `effect`"
type: exercise
file: "course/07-the-io-type.md"
anchor: exercise-71--pure-and-effect
hints: { file: "course/07-the-io-type.md", match: "Exercise 7.1" }
localCommand:
js: "cd js && node --test \"test/m07-*.test.js\""
scala: "cd scala && ./test.sh M07"
fsharp: "cd fsharp && ./test.sh M07"
- id: m07-ex-map
title: "Exercise 7.2 — `map` and `flatMap`"
type: exercise
file: "course/07-the-io-type.md"
anchor: exercise-72--map-and-flatmap
hints: { file: "course/07-the-io-type.md", match: "Exercise 7.2" }
localCommand:
js: "cd js && node --test \"test/m07-*.test.js\""
scala: "cd scala && ./test.sh M07"
fsharp: "cd fsharp && ./test.sh M07"
- id: m07-ex-sequence
title: "Exercise 7.3 — `sequence`, `draw`, `randomSeed`, `drawScene`"
type: exercise
file: "course/07-the-io-type.md"
anchor: exercise-73--sequence-draw-randomseed-drawscene
hints: { file: "course/07-the-io-type.md", match: "Exercise 7.3" }
localCommand:
js: "cd js && node --test \"test/m07-*.test.js\""
scala: "cd scala && ./test.sh M07"
fsharp: "cd fsharp && ./test.sh M07"
- id: m07-ex-trace
title: "Exercise 7.4 — `trace`, the pure interpreter"
type: exercise
file: "course/07-the-io-type.md"
anchor: exercise-74--trace-the-pure-interpreter
hints: { file: "course/07-the-io-type.md", match: "Exercise 7.4" }
localCommand:
js: "cd js && node --test \"test/m07-*.test.js\""
scala: "cd scala && ./test.sh M07"
fsharp: "cd fsharp && ./test.sh M07"
- id: m08
title: "Event streams"
lessons:
- { id: m08-text, title: "Event streams", type: text, file: "course/08-event-streams.md" }
- id: m08-ex-run
title: "Exercise 8.1 — `run`"
type: exercise
file: "course/08-event-streams.md"
anchor: exercise-81--run
hints: { file: "course/08-event-streams.md", match: "Exercise 8.1" }
localCommand:
js: "cd js && node --test \"test/m08-*.test.js\""
scala: "cd scala && ./test.sh M08"
fsharp: "cd fsharp && ./test.sh M08"
- id: m08-ex-keydowns
title: "Exercise 8.2 — sources: `keyDowns`, `keyUps`, `interval`"
type: exercise
file: "course/08-event-streams.md"
anchor: exercise-82--sources-keydowns-keyups-interval
hints: { file: "course/08-event-streams.md", match: "Exercise 8.2" }
localCommand:
js: "cd js && node --test \"test/m08-*.test.js\""
scala: "cd scala && ./test.sh M08"
fsharp: "cd fsharp && ./test.sh M08"
- id: m08-ex-map
title: "Exercise 8.3 — `map`, `filter`, `filterMap`"
type: exercise
file: "course/08-event-streams.md"
anchor: exercise-83--map-filter-filtermap
hints: { file: "course/08-event-streams.md", match: "Exercise 8.3 / 8.4" }
localCommand:
js: "cd js && node --test \"test/m08-*.test.js\""
scala: "cd scala && ./test.sh M08"
fsharp: "cd fsharp && ./test.sh M08"
- id: m08-ex-merge
title: "Exercise 8.4 — `merge`, `scan`, `startWith`"
type: exercise
file: "course/08-event-streams.md"
anchor: exercise-84--merge-scan-startwith
hints: { file: "course/08-event-streams.md", match: "Exercise 8.4" }
localCommand:
js: "cd js && node --test \"test/m08-*.test.js\""
scala: "cd scala && ./test.sh M08"
fsharp: "cd fsharp && ./test.sh M08"
- id: m09
title: "Wiring it together"
lessons:
- { id: m09-text, title: "Wiring it together", type: text, file: "course/09-wiring-it-together.md" }
- id: m09-ex-keytoaction
title: "Exercise 9.1 — `keyToAction`"
type: exercise
file: "course/09-wiring-it-together.md"
anchor: exercise-91--keytoaction
hints: { file: "course/09-wiring-it-together.md", match: "Exercise 9.1" }
localCommand:
js: "cd js && node --test \"test/m09-*.test.js\""
scala: "cd scala && ./test.sh M09"
fsharp: "cd fsharp && ./test.sh M09"
- id: m09-ex-classicactions
title: "Exercise 9.2 — the app: `classicActions`, `states`, `render`, `main`"
type: exercise
file: "course/09-wiring-it-together.md"
anchor: exercise-92--the-app-classicactions-states-render-main
hints: { file: "course/09-wiring-it-together.md", match: "Exercise 9.2" }
localCommand:
js: "cd js && node --test \"test/m09-*.test.js\""
scala: "cd scala && ./test.sh M09"
fsharp: "cd fsharp && ./test.sh M09"
- id: m09-ex-runio
title: "Exercise 9.3 — the runtime: `runIO` and `runApp`"
type: exercise
file: "course/09-wiring-it-together.md"
anchor: exercise-93--the-runtime-runio-and-runapp
hints: { file: "course/09-wiring-it-together.md", match: "Exercise 9.3" }
localCommand:
js: "cd js && node --test \"test/m09-*.test.js\""
scala: "cd scala && ./test.sh M09"
fsharp: "cd fsharp && ./test.sh M09"
- id: m09-ex-browserplatform
title: "Exercise 9.4 (JS only) — `browserPlatform`"
type: exercise
file: "course/09-wiring-it-together.md"
anchor: exercise-94-js-only--browserplatform
localCommand:
js: "cd js && node --test \"test/m09-*.test.js\""
scala: "cd scala && ./test.sh M09"
fsharp: "cd fsharp && ./test.sh M09"
- id: m10
title: "Extensions, all pure"
lessons:
- { id: m10-text, title: "Extensions, all pure", type: text, file: "course/10-extensions.md" }
- id: m10-ex-movesevery
title: "Exercise 10.1 — `movesEvery`"
type: exercise
file: "course/10-extensions.md"
anchor: exercise-101--movesevery
localCommand:
js: "cd js && node --test \"test/m10-*.test.js\""
scala: "cd scala && ./test.sh M10"
fsharp: "cd fsharp && ./test.sh M10"
- id: m10-ex-update
title: "Exercise 10.2 — `update`: `TogglePause`, `Restart`, `Frame`"
type: exercise
file: "course/10-extensions.md"
anchor: exercise-102--update-togglepause-restart-frame
hints: { file: "course/10-extensions.md", match: "Exercise 10.2" }
localCommand:
js: "cd js && node --test \"test/m10-*.test.js\""
scala: "cd scala && ./test.sh M10"
fsharp: "cd fsharp && ./test.sh M10"
- id: m10-ex-two-more-keys
title: "Exercise 10.3 — two more keys"
type: exercise
file: "course/10-extensions.md"
anchor: exercise-103--two-more-keys
localCommand:
js: "cd js && node --test \"test/m10-*.test.js\""
scala: "cd scala && ./test.sh M10"
fsharp: "cd fsharp && ./test.sh M10"
- id: m10-ex-the-frame-clock
title: "Exercise 10.4 — the frame clock"
type: exercise
file: "course/10-extensions.md"
anchor: exercise-104--the-frame-clock
hints: { file: "course/10-extensions.md", match: "Exercise 10.4" }
localCommand:
js: "cd js && node --test \"test/m10-*.test.js\""
scala: "cd scala && ./test.sh M10"
fsharp: "cd fsharp && ./test.sh M10"
- id: m11
title: "Capstone: Pong, by the method"
lessons:
- { id: m11-text, title: "Capstone: Pong, by the method", type: text, file: "course/11-capstone-pong.md" }
- id: m11-ex-model
title: "Exercise 11.1 — model"
type: exercise
file: "course/11-capstone-pong.md"
anchor: exercise-111--model
localCommand:
js: "cd js && node --test \"test/m11-*.test.js\""
scala: "cd scala && ./test.sh M11"
fsharp: "cd fsharp && ./test.sh M11"
- id: m11-ex-rules
title: "Exercise 11.2 — rules"
type: exercise
file: "course/11-capstone-pong.md"
anchor: exercise-112--rules
hints: { file: "course/11-capstone-pong.md", match: "Exercise 11.2" }
localCommand:
js: "cd js && node --test \"test/m11-*.test.js\""
scala: "cd scala && ./test.sh M11"
fsharp: "cd fsharp && ./test.sh M11"
- id: m11-ex-view
title: "Exercise 11.3 — view"
type: exercise
file: "course/11-capstone-pong.md"
anchor: exercise-113--view
localCommand:
js: "cd js && node --test \"test/m11-*.test.js\""
scala: "cd scala && ./test.sh M11"
fsharp: "cd fsharp && ./test.sh M11"
- id: m11-ex-keys-and-wiring
title: "Exercise 11.4 — keys and wiring"
type: exercise
file: "course/11-capstone-pong.md"
anchor: exercise-114--keys-and-wiring
hints: { file: "course/11-capstone-pong.md", match: "Exercise 11.4" }
localCommand:
js: "cd js && node --test \"test/m11-*.test.js\""
scala: "cd scala && ./test.sh M11"
fsharp: "cd fsharp && ./test.sh M11"
- id: ref
title: "Method and reference"
lessons:
- { id: ref-method, title: "The method", type: text, file: course/method.md }
- { id: ref-reference, title: "Reference", type: text, file: course/reference.md }
+110
View File
@@ -0,0 +1,110 @@
schema: 1
id: systemic-design
title: Systemic Design
pitch: Learn to build a rule-driven game engine by writing the systems that make its rules compose.
visibility: public
gating: sequential
root: /home/user/systemic-design-course
tracks:
- { id: rust, label: Rust }
- { id: ts, label: TypeScript }
mentor: mentor/SKILL.md
source: { kind: authored }
modules:
- id: m00
title: "The Rewrite Trap"
lessons:
- id: m00-lesson
title: "The Rewrite Trap"
type: text
file: "course/00-rewrite-trap/lesson.md"
- id: m01
title: "Handles and order of play"
lessons:
- id: m01-lesson
title: "Handles and order of play"
type: text
file: "course/01-handles/lesson.md"
localCommand: "node tools/start-module.mjs 01 <your-workspace>"
- { id: m01-scenarios, title: Scenarios, type: text, file: "course/01-handles/scenarios.md" }
- id: m02
title: "The action queue, depth-first"
lessons:
- id: m02-lesson
title: "The action queue, depth-first"
type: text
file: "course/02-action-queue/lesson.md"
localCommand: "node tools/start-module.mjs 02 <your-workspace>"
- { id: m02-scenarios, title: Scenarios, type: text, file: "course/02-action-queue/scenarios.md" }
- id: m03
title: "Deferred death"
lessons:
- id: m03-lesson
title: "Deferred death"
type: text
file: "course/03-death-step/lesson.md"
localCommand: "node tools/start-module.mjs 03 <your-workspace>"
- { id: m03-scenarios, title: Scenarios, type: text, file: "course/03-death-step/scenarios.md" }
- id: m04
title: "Interceptors and \"trigger twice\""
lessons:
- id: m04-lesson
title: "Interceptors and \"trigger twice\""
type: text
file: "course/04-interceptors/lesson.md"
localCommand: "node tools/start-module.mjs 04 <your-workspace>"
- { id: m04-scenarios, title: Scenarios, type: text, file: "course/04-interceptors/scenarios.md" }
- id: m05
title: "Behavior as data and Assemble"
lessons:
- id: m05-lesson
title: "Behavior as data and Assemble"
type: text
file: "course/05-assemble/lesson.md"
localCommand: "node tools/start-module.mjs 05 <your-workspace>"
- { id: m05-scenarios, title: Scenarios, type: text, file: "course/05-assemble/scenarios.md" }
- id: m06
title: "Grid, lexer and parser"
lessons:
- id: m06-lesson
title: "Grid, lexer and parser"
type: text
file: "course/06-parser/lesson.md"
localCommand: "node tools/start-module.mjs 06 <your-workspace>"
- { id: m06-scenarios, title: Scenarios, type: text, file: "course/06-parser/scenarios.md" }
- id: m07
title: "Resolving rules: NOT, identity, meta-nouns"
lessons:
- id: m07-lesson
title: "Resolving rules: NOT, identity, meta-nouns"
type: text
file: "course/07-rule-resolution/lesson.md"
localCommand: "node tools/start-module.mjs 07 <your-workspace>"
- { id: m07-scenarios, title: Scenarios, type: text, file: "course/07-rule-resolution/scenarios.md" }
- id: m08
title: "Push chains: validate, then commit"
lessons:
- id: m08-lesson
title: "Push chains: validate, then commit"
type: text
file: "course/08-push-chains/lesson.md"
localCommand: "node tools/start-module.mjs 08 <your-workspace>"
- { id: m08-scenarios, title: Scenarios, type: text, file: "course/08-push-chains/scenarios.md" }
- id: m09
title: "The turn pipeline"
lessons:
- id: m09-lesson
title: "The turn pipeline"
type: text
file: "course/09-turn-pipeline/lesson.md"
localCommand: "node tools/start-module.mjs 09 <your-workspace>"
- { id: m09-scenarios, title: Scenarios, type: text, file: "course/09-turn-pipeline/scenarios.md" }
- id: m10
title: "SpellGrid"
lessons:
- id: m10-lesson
title: "SpellGrid"
type: text
file: "course/10-spellgrid/lesson.md"
localCommand: "node tools/start-module.mjs 10 <your-workspace>"
- { id: m10-scenarios, title: Scenarios, type: text, file: "course/10-spellgrid/scenarios.md" }
@@ -0,0 +1,32 @@
import { describe, expect, test } from "bun:test";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { createCourseRepository, extractHints } from "../src/index";
const overlays = resolve(import.meta.dir, "../../../overlays");
const snakeRoot = "/home/user/functional-programming-course";
const systemicRoot = "/home/user/systemic-design-course";
describe("reference courses", () => {
test.skipIf(!existsSync(snakeRoot))("pure-snake loads cleanly", async () => {
const repo = await createCourseRepository([overlays]);
const c = repo.get("pure-snake")!;
expect(c.diagnostics.filter((d) => d.level === "error")).toEqual([]);
expect(c.status).toBe("ok");
expect(c.manifest!.modules).toHaveLength(13);
expect(c.lessonIndex.has("m03-ex-move")).toBe(true);
const hints = c.lessonIndex.get("m03-ex-move")!.lesson.hints!;
const md = await Bun.file(repo.resolvePath("pure-snake", hints.file)!).text();
expect(extractHints(md, hints.match)).toHaveLength(3);
expect(await repo.readLessonMarkdown("pure-snake", "m03-ex-move")).toStartWith("### Exercise 3.1");
});
test.skipIf(!existsSync(systemicRoot))("systemic-design loads cleanly", async () => {
const repo = await createCourseRepository([overlays]);
const c = repo.get("systemic-design")!;
expect(c.diagnostics.filter((d) => d.level === "error")).toEqual([]);
expect(c.status).toBe("ok");
expect(c.manifest!.modules).toHaveLength(11);
expect(c.manifest!.gating).toBe("sequential");
});
});