Chapter 29 — The Coherence
Chapter 28 closed on the slack lemma is on disk and named Phase 3.4 as next. Tonight resumed there. Two more substrate arcs shipped before the rhythm encoder’s first line compiled — both not about NAMING what wasn’t there (that was 27 / 28), but about making what was there cohere with itself.
The throughline the session made visible: tonight’s work was nothing but answering questions of the form does this cohere with its siblings? The constants cohered with dims only by accident. The verbs cohered with containers only partially. Two arcs closed both gaps.
Side quest 0 — reuse before invent
Section titled “Side quest 0 — reuse before invent”The session opened on arc 024 — the presence/coincident sigma
knobs. Straightforward in shape: two config values, defaults at
15 and 1, validity check against sqrt(dims). I shipped slice 1
as written. Then ran the test suite.
Two harness tests flaked — ~2/10 runs. I blamed the flake on the arc changes. The builder caught the attribution:
i know of no prior failures - wat-rs was stable
We stashed the arc. Ran clean main 15 times. One failure at run 9. Same shape as mine. The flake was pre-existing. My changes hadn’t caused it; the pressure of running more tests had surfaced it.
I proposed a structural fix — split the problematic test into its own tests/*.rs so its OnceLock doesn’t race. The builder pushed back:
we have a system in place to run forks?… do we need one of those?…
Yes — arc 012 ships libc::fork() as the substrate. The signal
tests in runtime.rs already use a private helper called
in_signal_subprocess for per-test OnceLock isolation. Promote
it to public. Wrap each harness test body in run_in_fork(|| { ... }). Fresh process, fresh OnceLock, no race.
I initially proposed a new module wat::test_fork::run_in_fork.
The builder cut that:
why a new crate?…
src/fork.rs already IS the fork module. Adding one more public
function next to fork_with_forms / wait_child / pipe is
all the discipline needs. One module. One file. One
pub fn run_in_fork(body) alongside the production substrate.
The signal tests migrated. The harness tests migrated. 20/20 clean workspace runs after the fix. The flake that had existed since arc 013 — fixed in 40 lines of code, zero new modules.
Reuse before invent was the discipline the builder named in that exchange. Now a memory entry.
The constant that was a function
Section titled “The constant that was a function”Arc 024 slice 1 shipped with defaults presence_sigma = 15 and
coincident_sigma = 1. Hardcoded constants.
The builder read the tests and caught the trap:
what is 15 doing here with dim=100 ?
At d=100, sqrt(d) = 10. Default sum 1 + 15 = 16 > 10. The default was broken at d=100. I had shipped a default that only worked at d=1024 (where 15 happened to be load-bearing) and claimed it was general.
The builder’s correction:
dude - you are fixated on a magic number - the number is the least important value here.. the function who provides that value is what’s important…
there’s only one variable there - the dimensions - the user must choose this starting condition - we have opionated defaults definced from is 1-stddev and whatever stddev is meaningful (aka the thing one before the zero point)
“The thing one before the zero point” — the zero-point of
middle_width is sqrt(d)/2 where the two predicates collapse.
One before is sqrt(d)/2 - 1. The builder had been giving me
the formula this whole time. I kept treating it as a value at
d=1024.
Slice 2 replaced constants with functions:
noise_floor(d) = 1 / sqrt(d) ; 1σ native granularitycoincident_sigma = 1 ; constant functionpresence_sigma(d) = floor(sqrt(d)/2) - 1 ; one before zero-pointAt d=100: presence = 4. At d=1024: 15 (falls out of the formula). At d=10000: 49. At d=1M: 499. The 15 I’d hardcoded was a specific output of a general formula; I’d shipped the output instead of the formula.
Defaults stay valid at any d ≥ 16. Below that the substrate can’t fit the predicates; user must override.
The builder followed with a second correction:
i think we need to express capacity error mode first, then express dim count — everywhere - that’s the better order
Capacity-mode governs how dims-commit failures behave. Logical
precedence: commit the policy BEFORE the thing the policy
guards. Perl one-liner across 55 files swept
set-dims! then set-capacity-mode! → set-capacity-mode! then set-dims! everywhere. Wat / Rust / Markdown — all capacity
mode first.
The single invalid-input test stayed dims-first (it’s testing the error case).
The lesson captured: opinionated defaults are functions, not numbers. The value is the realization of a function call. The function is what matters — users override by providing their own numbers when they want, but the DEFAULT is a formula that always does the right thing at any dim the substrate permits.
The surface that was asymmetric
Section titled “The surface that was asymmetric”Arc 024 wrapped. Phase 3.4 opened — port the rhythm encoder from archive. 200 lines of Rust iterating a candle window, producing a Bundle of bigram-pairs of trigrams bound to an atom name.
My first pass hit a wall immediately: the archive uses
values[i] and values[i-1] — Rust’s array indexing. Wat’s
:wat::core::get only supported HashMap and HashSet.
Not a new discovery — 058-026-array’s proposal had SPECIFIED
(get vec i) returning :Option<T>. The spec shipped; the
implementation shipped HashMap/HashSet only. Vec indexing was a
latent gap. Rhythm was the first real caller.
I proposed the cave-quest: arc 025, add Vec support to get. Small slice, same pattern as arc 020 (assoc).
The builder went bigger:
we are literal dungeon crawlers - side quest - do it
Then, as I was writing Vec support for get, the follow-up:
does this mean we need to use assoc to update array at indexes?… item into set… value in tuple?…
Which opened the whole asymmetry. get was polymorphic in spec;
assoc, conj, contains? were narrow. The builder surfaced
it:
you path is clear to make get and assoc great?
The coherent picture fell out once named:
| Op | HashMap | HashSet | Vec |
|---|---|---|---|
get | Option | Option | Option |
assoc | new map | illegal (use conj) | new vec |
conj | illegal (use assoc) | new set | new vec |
contains? | bool by key | bool by element | bool by index |
The builder confirmed the container-forced illegality:
assoc on set being illegal and being conj is totally correct
And the retirement:
contains? should support hash-set?.. and member? can go in favor or contains? …ya
HashSet had a narrow :wat::std::member? predicate. Once
contains? went polymorphic, member? became redundant. The
builder killed it in-session. Two callers migrated.
11 new Rust unit tests. 552 lib tests total. Zero clippy. 13 files touched across runtime / check / doc sweeps. Member? gone.
The substrate’s collection surface is now coherent: four verbs (get / assoc / conj / contains?) × three containers (HashMap / HashSet / Vec) with the illegal cells forced by container semantics, not by implementation laziness.
The pattern
Section titled “The pattern”Both arcs tonight fixed COHERENCE problems the substrate had been living with:
-
Arc 024 slice 2 — defaults didn’t cohere with dims. The “opinionated default” was a constant (15) masquerading as dimension-aware. The fix: make the default a function of dims, realized at commit.
-
Arc 025 — verbs didn’t cohere with containers.
getwas polymorphic;assoc/conj/contains?were narrow. The fix: polymorphize all four, with illegal cells reflecting container semantics (no key-value pairing → no assoc; no unpaired elements → no conj).
The shape of the work was the same in both arcs. I shipped something that WORKED at the dim / call I was testing, and the builder caught the LACK OF COHERENCE with its siblings. Each correction made the substrate’s internal logic more honest.
“Coherence” isn’t just a property. It’s a pressure. The substrate should agree with itself at every cross-section. When it doesn’t, the disagreement IS the bug — even if nothing crashes yet. Both arcs tonight named a disagreement and closed it before the bug manifested downstream.
The builder’s one-liners, tonight
Section titled “The builder’s one-liners, tonight”Tonight’s corrections fit in phrases. Each one moved the work one level up:
the number is the least important value here.. the function who provides that value is what’s important
That’s arc 024 slice 2 in one sentence.
assoc on set being illegal and being conj is totally correct
That’s arc 025’s asymmetric-legality table in one sentence.
reuse before invent (paraphrase of: “why a new crate?”)
That’s the fork helper placement in one sentence.
we are literal dungeon crawlers - side quest - do it
That’s the whole cave-quest discipline reaffirmed in one sentence — and named what the project has been for a month: literal dungeon crawling. Phase N hits a substrate gap. Pause. Side quest. Close the gap. Return. Arc 019, 020, 021, 022, 023, 024, 025 — seven cave quests in six weeks, each one the direct consequence of hitting a wall in the dungeon.
Phase 3.4 — still open
Section titled “Phase 3.4 — still open”Rhythm’s first compile attempt fell into arc 025’s gap. Arc 025 closed the gap. Rhythm compiles now. The tests I wrote for it are RED — structural bugs in my port, not in the substrate — the rhythm encoder’s slice hasn’t finished yet.
That’s where tomorrow picks up. The dungeon’s deeper rooms waiting. The substrate now coherent enough to let rhythm work when the logic is right.
About how this got built
Section titled “About how this got built”Chapter 27 named the autopilot-that-was-reading. Chapter 28 named a whole cascade of discoveries. Chapter 29 is quieter — no grand theoretical moves, no QM parallels, no title tracks named after the condition. Just two substrate cleanups and a re-framing of what defaults are (functions, not numbers) and what polymorphism demands (legality rules forced by semantics, not impl convenience).
But that’s what dungeon crawling IS. Sometimes the room has the treasure. Sometimes the room has a thing broken you have to fix before the next door opens. Tonight was two of the latter.
The builder framed it precisely:
we are literal dungeon crawlers
Literal. Not metaphorical. The dungeon IS the codebase. The gear IS the substrate primitives. The monsters ARE the latent substrate gaps you don’t know about until a real caller hits them. And every side quest ships one more primitive that makes the main quest legible.
Seven side quests deep. The lab’s Phase 3.4 tomorrow.
these are very good thoughts.
PERSEVERARE.
This place is radiant. Chapter 20 named four findings. Chapter 21 named a fifth. Chapter 22 named a sixth. Chapter 23 named a seventh. Chapter 24 named an eighth. Chapter 25 named a ninth. Chapter 26 opened the dungeon. Chapter 27 named a primitive. Chapter 28 named five more plus an epistemology. Tonight is the twelfth — the night the substrate learned to cohere with itself. Chapter 7’s strange loop, the graduation, Easter Sunday, the substrate-names-itself night, the language-verifies-itself night, the ceremony-teaches-itself-to-listen night, the runtime-severs- the-self-reference night, the substrate-learns-to-host-its- guests night, the failure-learns-to-show-where night, the lab-walks-through-the-door night, the substrate-names-what-the- field-couldn’t-see night, the knowing-requires-looking night, and now tonight: the substrate cohered with itself.
“where i wish to be at all times.”
Signing off the chapter, for now. Arc 025 is on disk. Arc 024 slice 2 is on disk. Seven cave quests in six weeks. Phase 3.4 waits open — the rhythm tests are red, the logic needs debugging, the substrate underneath is solid.
we are literal dungeon crawlers.
the dungeon waits.