chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30)

Repository-cleanup / DX pass folding three tracker items into one coherent
change, verified green end to end (`bin/x test` 49/0, `bin/x selfhost-test`
29/0, `bin/x test-tools` 29/0).

#28 — curate & categorise examples/
- 42 flat entries regrouped into intent-revealing subdirs: games/, rendering/,
  ecs/, events/, networking/, lang/, library/ (was lib/).
- chronorift dir-vs-file duplication resolved: the entry file and its import
  modules now live together under games/chronorift(.ludic).
- Every path reference updated repo-wide (test runner, editor-tool drivers,
  docs/site, design docs).
- New examples/README.md indexes the whole set with run commands.
- Showcase examples without a self-asserting entry (hello, events, net_rt) now
  get a compile-only rot guard in `bin/x test`, so nothing here rots silently.

#30 — text-diffable golden baseline
- The 4 binary selfhost/golden/*.ppm blobs are replaced by a single
  selfhost/golden/renders.sha256 manifest (SHA-256 per render). Hashes are
  byte-identical to the old PPMs, so the baseline is unchanged — only its form.
- game_case now compares framebuffer hashes; a regression shows as a changed
  hex line in review, not "binary files differ".
- New `bin/x golden` regenerates the manifest deliberately (review with
  `git diff selfhost/golden/renders.sha256`).

#27 — PPM & asset handling
- Headless renders now write build/out.ppm, never the repo root; `x app`,
  `x clean`, messaging and .gitignore updated to match. Nothing is written to
  the working root any more.
- Redundant local Kenney .zip archives removed (the art ships extracted;
  .gitignore already excludes *.zip). CC0 License.txt files retained.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-30 18:54:21 +03:00
parent 0bc5d76952
commit fb728bbefe
73 changed files with 364 additions and 194 deletions

View file

@ -133,7 +133,7 @@ handler Nav phase Update {
handler Draw phase Render { clear(0x0e0e16); ui_render(); present() }
```
See `examples/menu.ludic` for a complete title screen.
See `examples/games/menu.ludic` for a complete title screen.
## Types
@ -246,7 +246,7 @@ A constraint is evaluated **per candidate entity**, so it is the wrong place for
a guard that concerns the whole handler (re-reading `reg(R_MODE)` for every
entity). Keep whole-handler guards in the body of a handler with no `@Queries`,
wrapping an inline query — as `CleanBattle` does in
`examples/chronorift/combat.ludic`.
`examples/games/chronorift/combat.ludic`.
### Matching is lazy, not snapshotted
@ -391,9 +391,9 @@ disable Gravity # a whole model sits out every query
disable AiThink # a handler stops running each phase
```
See [`examples/toggle.ludic`](examples/toggle.ludic) for the three enable/disable
scopes, [`examples/detach.ludic`](examples/detach.ludic) for the structural
attach/detach pair, and [`examples/reason.ludic`](examples/reason.ludic) for
See [`examples/lang/toggle.ludic`](examples/lang/toggle.ludic) for the three enable/disable
scopes, [`examples/lang/detach.ludic`](examples/lang/detach.ludic) for the structural
attach/detach pair, and [`examples/lang/reason.ludic`](examples/lang/reason.ludic) for
reason-carrying teardown. The rest of the lifecycle roadmap (value-change hooks,
query-membership edges, keyed effects) is in
[LIFECYCLE-DESIGN.md](LIFECYCLE-DESIGN.md).
@ -402,9 +402,9 @@ query-membership edges, keyed effects) is in
`program`, `@Handles(Move)` names the handlers it uses. It parses and reads as
documentation; every declared handler still runs (registration is implicit).
See [`examples/annotations.ludic`](examples/annotations.ludic) (queries, computed
fields, one hook) and [`examples/lifecycle.ludic`](examples/lifecycle.ludic) (the
whole timeline), plus [`examples/toggle.ludic`](examples/toggle.ludic)
See [`examples/lang/annotations.ludic`](examples/lang/annotations.ludic) (queries, computed
fields, one hook) and [`examples/lang/lifecycle.ludic`](examples/lang/lifecycle.ludic) (the
whole timeline), plus [`examples/lang/toggle.ludic`](examples/lang/toggle.ludic)
(enable/disable). Scenes and their `on enter` / `on exit` lifecycle blocks are
implemented — see "Scenes & layers" below. (An annotation spelling,
`@OnEnter(Scene)` / `@OnExit(Scene)`, is a designed but not-yet-built convenience
@ -444,22 +444,22 @@ emit Hurt(entity: e, amount: 5) # fires every listener
architecture.** The game's own lifecycle becomes moddable with no hand-written
`emit`, at every scope:
- **program** — `@Public @OnStart`/`@OnQuit` → `program_start` / `program_quit`
(the top-level mod entry/exit points). See [`examples/program_events.ludic`](examples/program_events.ludic).
(the top-level mod entry/exit points). See [`examples/events/program_events.ludic`](examples/events/program_events.ludic).
- **models** — `@Public @OnSpawn(Enemy)`/`@OnDespawn(Enemy)` →
`model_Enemy_spawn` / `model_Enemy_despawn` (entity, + `EndReason` on despawn).
See [`examples/promote.ludic`](examples/promote.ludic).
See [`examples/events/promote.ludic`](examples/events/promote.ludic).
- **properties** — `@Public @OnAttach/@OnDetach/@OnEnable/@OnDisable(P)` →
`prop_<P>_attach` / `_detach` / `_enable` / `_disable`. See [`examples/prop_events.ludic`](examples/prop_events.ludic).
- **scenes** — a `public` scene → `scene_<S>_enter` / `scene_<S>_exit`. See [`examples/scene_events.ludic`](examples/scene_events.ludic).
`prop_<P>_attach` / `_detach` / `_enable` / `_disable`. See [`examples/events/prop_events.ludic`](examples/events/prop_events.ludic).
- **scenes** — a `public` scene → `scene_<S>_enter` / `scene_<S>_exit`. See [`examples/events/scene_events.ludic`](examples/events/scene_events.ludic).
- **layers** — a `public` layer, with `enable layer L` / `disable layer L`
flipping the layer on and off (its handlers stop while hidden) →
`layer_<L>_show` / `layer_<L>_hide`. See [`examples/layer_events.ludic`](examples/layer_events.ludic).
`layer_<L>_show` / `layer_<L>_hide`. See [`examples/events/layer_events.ludic`](examples/events/layer_events.ludic).
- **`cancellable` events are decisions, not just notifications.** A listener on a
`cancellable` event may `cancel` it (a foreign listener sets the payload's
trailing `cancelled` flag); `emit E(…)` used as an *expression* yields that flag,
so the caller applies the action only when it wasn't vetoed — the Bukkit/DOM
`preventDefault` shape. See [`examples/cancel.ludic`](examples/cancel.ludic).
`preventDefault` shape. See [`examples/events/cancel.ludic`](examples/events/cancel.ludic).
```ludic
# doc-check: skip — illustrative
@ -567,7 +567,7 @@ extern function c_hypot(a: fixed, b: fixed) -> fixed = "hypot_fx" # bind a C s
`extern fn … = "symbol"` declares a foreign function and binds it to a symbol
resolved at link time; pass `-L`/`-l` to ludicc to link its library. This is how
Ludic calls anything with a C ABI — including a shared library built from
another `.ludic` file (see `examples/lib/`).
another `.ludic` file (see `examples/library/`).
## Statements
@ -636,7 +636,7 @@ match tile {
`machine` turns a register into an explicit state machine: it dispatches on the
register's value to the matching `state`, and `become` transitions to a named
state (no more `if phase == N` chains). See the co-op battle in
`examples/chronorift/combat.ludic`:
`examples/games/chronorift/combat.ludic`:
```ludic
# doc-check: skip — illustrative: elided bodies
@ -671,7 +671,7 @@ A variant is a **compile-time `int`** accessed as `Enum.Variant` (`Action.Guard`
is `1`), numbered from `0` by declaration order, so it works anywhere an int does
— `match` patterns, comparisons, `set_reg`. Enums are a naming layer over `int`:
there is no distinct enum runtime type yet, so an enum value lives in an ordinary
`int` or register (and is saved with it). See `examples/chronorift/combat.ludic`,
`int` or register (and is saved with it). See `examples/games/chronorift/combat.ludic`,
whose battle menus dispatch on `KnightAct`/`MageAct` instead of `0..3`.
## Expressions
@ -792,15 +792,15 @@ Emacs, Sublime and Zed, are in `tools/editors/` — see
## Working programs
- `examples/chronorift.ludic` — a co-op JRPG (overworld, dungeon, boss, shop,
- `examples/games/chronorift.ludic` — a co-op JRPG (overworld, dungeon, boss, shop,
save) using CC0 Kenney sprites. Split across `chronorift/*.ludic` via `import`,
built on models.
- `examples/menu.ludic` — a retained-UI title screen (9-slice panel, TrueType
- `examples/games/menu.ludic` — a retained-UI title screen (9-slice panel, TrueType
labels, focusable buttons).
- `examples/snake.ludic` — Snake, no assets — same compiler, proving generality.
- `examples/games/snake.ludic` — Snake, no assets — same compiler, proving generality.
```bash
bin/x app examples/snake.ludic && ./build/snake
bin/x app examples/games/snake.ludic && ./build/snake
```
## Not yet implemented
@ -826,7 +826,7 @@ self-hosting; their lowerings are in
## Scenes & layers
> **Implemented (S0).** `scene`, `layer`, and the `on enter` / `on exit` hooks
> compile; [`examples/scenes.ludic`](examples/scenes.ludic) runs and is checked
> compile; [`examples/lang/scenes.ludic`](examples/lang/scenes.ludic) runs and is checked
> by `bin/x test`. A scene lowers to a `machine` the compiler writes for you: one
> implicit active-scene register, states numbered by declaration order, and
> `become` as two direct calls plus a store. Richer scene features (the overlay
@ -878,7 +878,7 @@ scene Overworld {
any single phase, and a `become` in `Update` is visible to that same frame's
`Render`.
[`examples/scenes.ludic`](examples/scenes.ludic) is a runnable, tested example
[`examples/lang/scenes.ludic`](examples/lang/scenes.ludic) is a runnable, tested example
of these rules.
## Queries in a handler signature
@ -895,7 +895,7 @@ handler CleanBattle phase LateUpdate { despawn self() }
This is exactly equivalent to wrapping the body in
`for (Battle, Pos) in query [Battle, Pos, {Foe}] where Battle.hp <= 0 { … }` —
same lowering, same semantics. The body runs once per matching entity and
`self()` is that entity. `examples/qdecl.ludic` is a working example.
`self()` is that entity. `examples/lang/qdecl.ludic` is a working example.
Mutation during iteration follows the same rules as an inline query, because it
is the same loop: entities are visited by ascending id, `despawn` of the current