# Step 10 — Module BEGIN bodies + self-reference (uncommitted) 143/143 tests green (137 + 3 run + 3 rejection), mc64 boot + example green, both Showcases unchanged (157, 83). ## Goal Complete single-file local modules: optional `BEGIN..END` init bodies run at startup in declaration order, including calls to the module's own procedures (plain and `M.`-qualified). ## What was built - `MGen`: module inits compile as parameterless proper procedures (`ModInitBegin` allocates a proc-table number + `ENTER`, `ModInitEnd` emits `LEAVE`); `BeginBody` calls them in order after const inits, before program statements. Max 8 inits (module limit), sharing the 64 proc numbers (print helper still takes `maxNum+1` at `EndModule`). - `M2c.atg` `ModuleDecl`: optional `BEGIN StatSeq`, jumped over inline (`Jmp`/`DefLabel` like procedures), skipped entirely for nested/duplicate modules. `RETURN` in a body is already 232 (not in a proc); header updated (`no bodies` dropped). - `SymTab`: `modLevs[]` records each module's body scope level; `SelfKind(mod, exp)` resolves `M.x` against the live body scope (the export table only fills at `END`), guarded to body level so nested scopes can't hijack; `SelfQual` builds `M.x` for codegen. - `M2c.atg` call sites use it: `DesignTail` (vars/consts/procs), `CallTail` (by-number via `ProcNum`), `Fact` (return types), `QualIdent` (`M.T` inside `M`). ## Tests — 143/143 (137 + 3 + 3) 3 run: `b_init` 42, `b_order` 111 (two inits, order + cross-module read), `b_call` 15 (plain `Inc()` and `M.Inc()` from init). 3 rejections, each exactly one error: `b_bad_ret` 232, `b_bad_nest` 230, `b_bad_dup` 200. ## Bugs found and fixed 1. `M.Inc()` inside `M` gave 201: the export table is empty until `END`. Self-scope fallback fixes vars, consts, procs, and types uniformly. 2. gm2 16.0.1 ICE (deterministic, no source location) on `x = -1 & (y = -1)` in generated `M2cP.mod` — minimized to `IF a = -1 & (b = -1) THEN END` in 20 lines. Workaround: nested `IF`s with `< 0` (single `F() = -1` and `(x < 0) & ...` shapes compile). New rule: never compare against a negative literal inside an `&` chain. ## Known limits (deferred) `DEFINITION`/`IMPLEMENTATION` split (the remaining big item), `WITH`-field `VAR` actuals, `CHAR`/`BOOLEAN` tails as `VAR` actuals, open-to-open. ## Post-step fix — init/proc table-slot collision (new commit on top) Showcase3 (`A.x*100 + B.y + C.cnt + D.val` = 168) exposed a real bug: with an early plain init plus a later procedure-owning module, the run printed 72 (`A.x` = 0, `C.cnt` = 15). Root cause: `ModInitBegin` took `maxNum+1` from emission order while `SymTab` numbers procedures from a separate declaration-order pool — so module A's init stole `C.Inc`'s number 1, and `ProcEntry(1)` for `Inc` overwrote the init's table cell (startup then called `Inc` instead of A's init: 5 + 10 = 15 in `cnt`, `x` never assigned). Single-module tests passed only because each procedure body was emitted before its module's init. Fix: init numbers now come from `SymTab.AllocInitNum()`, sharing the `nProcs` pool (`procs[]` entry initialized like a parameterless proper procedure). Regression `b_mix` (110) locks the shape; suite 144/144, Showcases unchanged (157, 83), Showcase3 prints 168.