Add enable/disable across ECS scopes + @OnEnable/@OnDisable hooks

Three enable/disable statement scopes, each a reversible flag flip:
  - `disable P on e` / `enable P on e` — one property on one entity. Clears
    the has-flag so queries stop matching; field data persists in storage, so
    enable restores it untouched. @OnDisable(P)/@OnEnable(P) handler hooks run
    at the toggle point with the property bound by name.
  - `disable Model` / `enable Model` — @ME_<Model> global flag; the model's
    entities drop out of every query while disabled.
  - `disable Handler` / `enable Handler` — @HE_<Handler> global flag; the
    handler stops being called each phase while disabled.

Nothing is copied or freed — each toggle is one global store or one has-flag
store. Reduces entirely to existing ECS machinery (has-flags, kind filter,
per-phase call guards), so the data-oriented model is untouched.

New AST node S_TOGGLE; emit_toggle lowers it. Query {Model} filter now ANDs
@ME_; phase calls now guard on @HE_. Parser gains enable/disable statements
and @OnEnable/@OnDisable annotations.

Vocabulary: `on` promoted from RESERVED to CLAUSE (parser now dispatches on
it); enable/disable added as STMT keywords — synced across ludic_syntax.h,
the TextMate grammar, and LudicTokens.kt (check-vocabulary.py clean).

examples/toggle.ludic demonstrates all three scopes (prints 6 0 7 1 0);
test.sh smoke asserts it. Reseeded; C-free fixpoint holds; goldens identical.
Also: stop tracking tools/.idea/ (gitignored).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-27 23:05:47 +03:00
parent bfc868c546
commit 0bded528df
17 changed files with 4772 additions and 3911 deletions

View file

@ -323,15 +323,46 @@ boot ── @OnStart ─▶ spawn ── @OnAttach(P), @OnSpawn(M) ─▶ …
@OnQuit handler Save { save() } # once, at shutdown
```
**Enable / disable.** `enable` and `disable` are statements that flip something on
or off without destroying it. There are three scopes:
- **`disable P on e` / `enable P on e`** — one *property* on one entity. Disabling
clears the entity's has-flag, so queries stop matching it, but the field values
stay in storage — a later `enable` restores them untouched. `@OnDisable(P)` and
`@OnEnable(P)` are handler annotations that run at the toggle point with the
property bound by name (like a one-entity `@OnSpawn`).
- **`disable Model` / `enable Model`** — a whole *model*. Its entities drop out of
every query while disabled; the entities and their data are left alone.
- **`disable Handler` / `enable Handler`** — a *handler*. It stops being called
each phase while disabled, and resumes on `enable`.
Each toggle is one global flag flip (or one has-flag store), so nothing is copied
or freed — enable/disable is cheap and fully reversible.
```ludic
# doc-check: skip — enable/disable
@OnDisable(Shield) handler Down { play("shield_break.wav") }
@OnEnable(Shield) handler Up { play("shield_up.wav") }
disable Shield on self() # this entity loses its shield; data kept for later
enable Shield on self() # shield back, amount unchanged
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 all three scopes in one
frame. Still to come: **`@OnDetach`** (the paired hook for a property leaving,
needing the same per-property runtime dispatch as despawn).
**`@Handles` — the handlers a program drives.** Written in front of the
`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). Still to come: **`@OnDetach`** (needs the same runtime kind
dispatch as despawn, per property) and **scene** hooks (`@OnEnter`/`@OnExit`),
which wait on `scene` support landing in the compiler.
whole timeline), plus [`examples/toggle.ludic`](examples/toggle.ludic)
(enable/disable). Still to come: **scene** hooks (`@OnEnter`/`@OnExit`), which
wait on `scene` support landing in the compiler.
## Structs, arrays and slices
@ -425,7 +456,8 @@ another `.ludic` file (see `examples/lib/`).
`let x = expr` · `x = expr` (`+= -= *= /=`) · `if/else` · `when cond { }`
(if-without-else) · `while cond { }` · `for i in a .. b { }` (numeric range) ·
`for (…) in query […] { }` · `break` · `continue` · `return` · `spawn` ·
`despawn` · `match` · `machine`.
`despawn` · `enable` / `disable` (a property `on e`, a model, or a handler) ·
`match` · `machine`.
**Statements are separated by a newline or `;`** (both lex to the same separator
token). Two statements may not sit adjacent with only spaces between them — the