- word_id_map / recycled_word_ids are heap tables (one shared capacity,
64 doubling), grown all-or-nothing by allocate-copy-free. Numbering is
unchanged, so boot parity is unchanged.
- vm_create_word() takes the id before linking; on failure it frees the
entry, sets vm->error, logs LOG_ERROR and counts
(vm_dictionary_word_id_refusals()). No word runs with WORD_ID_INVALID.
- Readers of the id space bound by vm->next_word_id, not DICTIONARY_SIZE.
- transition_heat gets its own capacity and grows on record (was sized
once -- a heap overflow under a dynamic bound); every reader bounds by it.
- Stadium id->cell rows start empty and grow on dispatch; a failed grow is
logged, counted and not admitted. S-2 (the cell pool) stays open.
Probe (reverted), identical on amd64/aarch64/riscv64: +2000 words get real
ids (capacity 4096); a word at id 2738 lands 200x in the rolling window and
is Stadium-admitted; FORGET recycles across the old boundary; an injected
grow failure refuses the word whole (NULL, error, counter, dictionary
unchanged) and the retry succeeds. 3-ISA acceptance passes; boot parity
(531, 0x572daac83b797332) and the dict_hash set unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>