plan.md: split row 14 into the MA ladder (14a-14f) — multi-actor is not one deliverable #50

Manually merged
dferreira merged 4 commits from feat/050-plan-md-split-row-14-into-the-ma-ladder-14a-14 into master 2026-03-04 13:00:00 +01:00
Owner

plan.md: split row 14 into the MA ladder (14a-14f) — multi-actor is not one deliverable

Along the way:

  • decisions(scheduler): mailbox-full = RAISE, never backpressure (owner, 2026-07-02)
  • plan.md: MA ladder restructured — primitives first, delivery-sized rows; scheduler model FULLY decided
  • decisions+plan: for/while loops, Clock::tick, and the park (owner, 2026-07-02)

Touches 2 files: decisions-scheduler-model.md, plan.md.

plan.md: split row 14 into the MA ladder (14a-14f) — multi-actor is not one deliverable Along the way: - decisions(scheduler): mailbox-full = RAISE, never backpressure (owner, 2026-07-02) - plan.md: MA ladder restructured — primitives first, delivery-sized rows; scheduler model FULLY decided - decisions+plan: for/while<N> loops, Clock::tick, and the park (owner, 2026-07-02) Touches 2 files: `decisions-scheduler-model.md`, `plan.md`.
Author
Owner

Measured. It is a named constant in decisions-scheduler-model.md now, with a line saying how.

Measured. It is a named constant in `decisions-scheduler-model.md` now, with a line saying how.
Author
Owner

Comment added on child in plan.md. It was obvious when I wrote it and it will not be in a month.

Comment added on `child` in `plan.md`. It was obvious when I wrote it and it will not be in a month.
Author
Owner

Updated. The point about decisions-scheduler-model.md turned out to be a real bug, so thank you.

Updated. The point about `decisions-scheduler-model.md` turned out to be a real bug, so thank you.
dferreira requested changes 2026-03-04 12:55:00 +01:00
Dismissed
dferreira left a comment

I do not want to merge while the error path in decisions-scheduler-model.md is silent. The rest is fine.

I do not want to merge while the error path in `decisions-scheduler-model.md` is silent. The rest is fine.
@ -70,0 +105,4 @@
to actor X". The timer queue is scheduler state; a DUE tick enqueues an
ORDINARY message (FIFO, the normal mailbox path — no special delivery, so
determinism and the meta stream are untouched). This is `snake.md`'s tick
stone in its real form, and the MMO server's ~20 ticks/s driver.
Owner

Where does the number beside stone come from? It reads as measured rather than chosen, and nothing on the line says which.

Where does the number beside `stone` come from? It reads as measured rather than chosen, and nothing on the line says which.
@ -107,0 +106,4 @@
| 14a | **MA1 · the missing primitives — loops + byte width** *(design fully closed: `decisions-scheduler-model.md` — cooperative FIFO, per-state writer, run-to-completion CONFIRMED, mailbox-full = RAISE, loops/tick/park decided)*: (1) **`for` + `while<N>` loops** (LC9's loop half pulled forward; owner 2026-07-02: recursion-only is not enough): `for` over a bounded range/collection, `while<N>` with a declared bound — the iterative twin of `rec<N>`; NO unbounded loop exists (indefinite execution = a scheduled actor, never a spin); front-end + CFG back-edge lowering (LC2 blocks); (2) **recursion EXECUTION** gated (`fib_runs`, pulled from Rung 1 — FS5.2 built parse/check/size; plausibly a gate, not a feature); (3) **`Memory::load/store::<u8>`** — the third machine-op width (FS4 pattern); (4) **`Bytes.ck` in PURE CkLang** (loops over u8 loads/stores) promoted into the live stdlib, then **`Meta.ck`'s `append_str`/`str_len` special forms rewritten as real CkLang over `Bytes.ck`**. After this row the only pass special forms left are the irreducible floor ISA (cells, `dispatch`, `raise_word`) — registered `= intrinsic` leaves, the `Memory::load` tier | `for`/`while<N>` corpus stones (exit-code loops); `fib_runs` executes; `Bytes::copy` round-trips; Meta.ck streams byte-identical after the rewrite (goldens prove it) |
| 14b | **MA2 · messages can exist — union-ABI copy + `send`/`receive` front-end** (one agent, two checkpoint commits — the LC6/LC7 pattern): first the **union-ABI sliver** — a payload-union value crosses a call boundary **by copy** (the LC5 scope-out; messages ARE payload variants, nothing sends until this lowers); then the **front-end** — receive slots parse fully (snake's `World.ck` E0110/E0105 trap rows ratchet DOWN), sends resolve + typecheck (message type ∈ the actor's declared receive set; synthesis-only, plan §5) | union-copy corpus stone round-trips; type goldens; `snake/trap.md` shrinks; an honest "send does not lower yet" error |
| 14c | **MA3 · `Mailbox.ck` + `Scheduler.ck` + `Clock.ck` — 100% CkLang** (one agent, checkpoint commits): the bounded FIFO ring in pure CkLang over `Memory`/`Arena`/`Bytes` (full → `Panic::raise`, the locked decision; generic-free bootstrap like `Arena.ck` if LC6's cross-module-template scope-out bites — report); the run loop in pure CkLang (bounded drain via `while<N>`/`for`; FIFO writer queue; per-actor arena-cell swap via `Supervisor::adopt`; handler dispatch via `dispatch`); `send` lowering to enqueue + a self-send driver; **`Clock::tick`** — the timer queue as scheduler state, "every X ms send `Tick` to actor" with a DUE tick enqueuing an ORDINARY message (FIFO, determinism untouched); **the park** — no runnable + timers pending → `nanosleep`(35) until next due (syscall table + `clock_gettime`(228)); no timers + no messages → drain-exit. NOTHING SPINS. `epoll` stays Rung 3's. **Bonus: real meta-event timestamps** (`clock_gettime` wired ⇒ the timestamp-0 residue retires) | driver-level enqueue/dequeue gate (FIFO + bound + full-raise asserted); the `tick` stone (snake.md Rung 2): a `Clock::tick`-driven actor accumulates N ticks → exit = count, CPU parked between ticks (no busy loop — assert wall-clock ≥ N×interval); meta events carry real timestamps |
| 14d | **MA4 · multi-actor + send-is-serialize → `counter`**: `Supervisor.ck` spawns a child (id allocation, child arena — the ids stop being constants), cross-actor send through the serialize→transmit→deserialize codec in CkLang over `Bytes.ck` (only `imm` + tags/ActorIds cross) | **gate: `tests/corpus/counter` runs** — joined state serialized through ONE mailbox, deterministic count, 2-actor lifecycle on the meta stream |
Owner

I had to read child twice. Nothing wrong with it, but one line of comment would save the next person the second read.

I had to read `child` twice. Nothing wrong with it, but one line of comment would save the next person the second read.
dferreira approved these changes 2026-03-04 12:55:00 +01:00
dferreira left a comment

Both answered, and decisions-scheduler-model.md reads right now. Merging.

Both answered, and `decisions-scheduler-model.md` reads right now. Merging.
Owner-confirmed split: the floor units (Mailbox.ck, Scheduler.ck,
Supervisor spawn-child) are 'just CkLang' now, but four compiler gaps
remain and each is its own slice: MA0 design confirmation with the owner
(run-to-completion-per-message as the yield model + the mailbox-full
decision — the two open rows in decisions-scheduler-model.md); MA1 the
LC5 union-ABI sliver (messages ARE payload variants — nothing sends
until a union value crosses a call boundary by copy); MA2 send/receive
front-end (snake's World.ck trap rows ratchet down); MA3 Mailbox.ck;
MA4 Scheduler.ck + the tick stone (NO futex/epoll — counter terminates
when the queue drains, blocking is Rung 3's); MA5 multi-actor spawn +
send-is-serialize, gated on counter running with a deterministic count
+ 2-actor lifecycle stream.
A send into a full Mailbox<Msg,N> raises (the LC10 model: trappable by
the sender; untrapped -> the sender actor crashes with the reason on the
meta stream — also the observable for sizing N). NOT backpressure:
parking a sender would interfere with deterministic memory — the parked
message must live somewhere while it waits, unaccounted state outside
every declared bound (bounded-everything: overflow is designed-in, never
emergent). plan.md MA0 row updated — the only remaining MA0 item is the
one-word run-to-completion-per-message confirmation.
Run-to-completion-per-message CONFIRMED by owner (a receive slot is a
handler; yield = the handler returning; no stack switching anywhere) —
decisions-scheduler-model.md has no open rows left. MA0 dissolves.

New ladder (rows 14a-14d): MA1 the two missing primitives — recursion
EXECUTION gated (fib_runs pulled forward from Rung 1; likely a gate not
a feature) + Memory::load/store::<u8> + Bytes.ck in pure CkLang
(rec<N> loops over u8 stores) + de-special-forming Meta.ck's
append_str/str_len into real CkLang; after MA1 the only pass special
forms are the irreducible floor ISA (cells, dispatch, raise_word) —
the same registered-intrinsic tier as Memory::load. MA2 union-ABI copy
+ send/receive front-end (one agent, two checkpoint commits). MA3
Mailbox.ck + Scheduler.ck 100% CkLang (rec<N>-BOUNDED drain loop —
even the scheduler carries a declared bound; full -> raise) + the tick
stone. MA4 multi-actor + serialize -> counter.

Slice-sizing note recorded (owner): rows sized for one warm-context
agent across checkpoint commits — verifiable checkpoints, no full
context reload per commit. The 100%-CkLang bar defined: every line of
Mailbox/Scheduler/Supervisor logic is stdlib CkLang source; only the
grep-intrinsic-enumerable floor ISA may be compiler-desugared.
Loops: recursion-only is not enough — for (bounded range/collection) +
while<N> (declared bound, the iterative twin of rec<N>) join MA1; NO
unbounded loop construct exists — anything indefinite is a scheduled
actor, never a spin (owner: a while(true) at our speeds would burn the
CPU down; indefinite execution lives in exactly one place, the
scheduler's park). Clock::tick: periodic message scheduling — the timer
queue is scheduler state, a due tick enqueues an ORDINARY message
(FIFO, determinism untouched); snake's tick stone in its real form +
the MMO 20-ticks/s driver. wait = the scheduler's park, not a user
primitive: nanosleep(35) until next due timer, drain-exit otherwise;
epoll upgrades the same seam at Rung 3. Bonus wired into MA3:
clock_gettime(228) gives meta events real timestamps — the timestamp-0
residue retires. MA1/MA3 rows updated; tick stone now asserts the park
(wall-clock >= N x interval, no busy loop).
dferreira manually merged commit 2aeff8c9a7 into master 2026-03-04 13:00:00 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/cklang-bootstrap!50
No description provided.