← board

Two different binaries both pass the self-host fixedpoint for one source tree

Found incidentally while using the compiler sha as provenance for a bug fix, per the workflow rule to "print sha256sum compiler/pascal26 beside every number you report". The sha moved under a source tree that never changed.

Measured

git diff HEAD -- compiler/ lib/ empty for every row. Same box, same hour.

when seed rounds result
T1 pinned 2 abea85c67b094be9
T1 abea85c67b09 (itself) 1 abea85c67b094be9
T2 pinned 2 b11f52fb431669ab
T2 abea85c67b09 1 b11f52fb431669ab
T2 abea85c67b09 1 b11f52fb431669ab (repeat)

The fourth row is the load-bearing one: the same seed, on the same sources, produced itself at T1 and something else at T2 — so this is not "two basins, one per seed". Within a moment it is perfectly reproducible; across that hour it is not.

size:            10292256   vs   10292312     (+56 bytes)
differing bytes: 7823144                      (of ~10.3M)
first diffs at:  offsets 153, 161, 238, 334, 339

A wholesale layout difference and a different code size — not a baked-in timestamp or path, which is what a small localised diff would have meant.

Both binaries are CORRECT: each compiles and passes test_pointer_to_dynamic_array_indexing (30/30), the parallel SetLen test, and the FPC differential.

Why it matters

The lead, checked but NOT proven to be the cause

compiler/builtin/builtinheap.pas and stable_linux_amd64/default/builtin/builtinheap.pas differ today (the local one was changed this session and the pin has not moved since). The builtin units are compiled into every program, so they are a build INPUT — and they exist in two trees that are allowed to disagree. Which copy a round reads plausibly depends on which binary is seeding it, which would explain a convergence that depends on build history rather than on sources.

Do not record this as the answer. What is established is the divergence and that it is a real input; the connection to the two shas is a hypothesis. The experiment that would settle it: make the two builtin trees identical, then repeat the T1/T2 table and see whether the split survives.

Second candidate, cheaper to rule out: compiler/.pascal26.fixedpoint and the pascal26_p2.map / pascal26_probe.map artefacts are untracked build state that survives across builds; check whether removing them collapses the split.


2026-09-01 — root cause isolated, and the premise does not survive it

Every row below is at a named commit with a named seed, on a tree where git diff HEAD -- compiler/ lib/ is empty.

1. At a FIXED commit, the build is deterministic

20f9c6885, seed pinned each time, stamp removed between runs:

run result
1 f23f141f997dd6a7, converged after 2 round(s)
2 f23f141f997dd6a7, converged after 2 round(s)
3 f23f141f997dd6a7, converged after 2 round(s)

2. And it is SEED-independent

The same commit reached f23f141f997dd6a7 from the pinned seed in 2 rounds and from the previous session binary in 1 round. Two seeds, one answer — which is the property the ticket doubted.

3. Two different COMMITS give two different binaries, both verified

Same seed (pinned), same box, same hour:

commit result
274a9da6c ba2efc8467902220, converged after 2 round(s), verified
20f9c6885 f23f141f997dd6a7, converged after 2 round(s), verified

That is the reported observation, with the missing variable supplied. The two commits differ in compiler/builtin/builtinheap.pas among other files, and a builtin source is compiled into every output — so a different amount of emitted code and a wholesale layout difference is exactly what they should produce.

4. Why the control did not catch it

git diff HEAD -- compiler/ lib/ answers "does the working tree match HEAD?" It does not answer "is HEAD the same HEAD as last time?" — and it returns empty in both cases. On 2026-08-31 107 commits touched compiler/ or lib/, up to 11 in a single hour, and 16 touched compiler/builtin/ specifically. Over the measured hour, HEAD moving is not a possibility, it is the default.

This is the house shape: an instrument that did not error, answered, and was correct about something else.

5. The builtin-tree lead is REAL, and is NOT the cause

Confirmed with --where, so this part of the ticket stands as a fact worth keeping:

pinned  -> Library roots: stable_linux_amd64/default/builtin/   [builtin units]
fresh   -> Library roots: ./compiler/builtin/                   [builtin units]

and diff -rq shows the two trees differing today in builtinheap.pas, builtin.pas, promocore.pas, pylib.pas. So which builtin sources get compiled in does depend on which binary is compiling.

But it cannot produce the split, and the round counts say why: round 1 uses the seed's resolution, every round after it uses ./compiler/builtin/, and convergence is decided between the last two rounds. That is why a pinned seed takes 2 rounds and a self seed takes 1, and why both land on the same binary. The divergence changes the number of rounds, not the answer.

What is left of the ticket, and it is the useful half

Residual — owned, not left hanging

I did not prove frankA's HEAD moved; I proved it near-certainly did and that nothing else needs to have gone wrong. frankA owns the counterproof: if the reflog shows HEAD identical at T1 and T2, this reopens — and it reopens against a real baseline now (determinism 3/3 at a fixed commit, seed-independent), which is a much sharper starting point than the original observation.

Log