diff --git a/AUDIT-2026-07.md b/AUDIT-2026-07.md index ec5ba03..d95a0a8 100644 --- a/AUDIT-2026-07.md +++ b/AUDIT-2026-07.md @@ -75,6 +75,48 @@ Applied consistently at all `SeedF` definitions (`:682`, `:1756`, `:2066`, `:220 --- +#### ⛔ CORRECTION 2026-07-27 — **the fix above is WRONG. Do not apply it.** + +It bounds `SeedF` but leaves the **multiplier** in place, and the multiplier is where the magnitude +comes from. `SeedF · 97.7` with `SeedF ≤ 16383` still reaches **1.6e6**, where the float ULP is: + +| | max coord term | ULP there | vs the ~0.02/voxel coordinate step | +|---|---|---|---| +| `Seed = 1000` today | 9.8e4 | 0.012 | 0.6× — fine | +| **audit's proposed fix, worst case** | **1.6e6** | **0.19** | **9.5× — still lattice-snaps** | + +So the "fix" would have made the bug *less catastrophic* while leaving it live, and — worse — +**closed the ticket.** It would have cost a build and a world re-roll to discover that mid-range +seeds still degrade. (Found by doing the arithmetic before applying it, not after.) + +**The actual fix: bound the OFFSET, not the seed — i.e. delete the multipliers.** + +The `· 7.3f … · 97.7f` multipliers exist only to decorrelate the ~40 noise call sites from each +other. That is a *hashing* job, and hashing does it better: give each site its own offset drawn from +the full seed entropy, already in final units, with no multiplier to re-inflate it. + +```cpp +// VoxelHash — one definition, so the bound cannot drift per site. +// Site-salted, final units, bounded: ULP at 16384 is 0.002 = 10% of a voxel step. +FORCEINLINE float SeedOffset(int32 Seed, uint32 Site) +{ + return (float)(Mix((uint32)Seed ^ (Site * 2654435761u)) & 0x3FFF); +} +``` +`WorldX * SF + SeedF * 83.1f` → `WorldX * SF + VoxelHash::SeedOffset(Seed, kSiteScallopX)`. + +**Why this is strictly better, not just smaller:** bounding `SeedF` alone would squeeze all seeds +through 16384 distinct noise offsets *shared across every site*, so two colliding seeds would give +identical noise everywhere. Per-site salting means two seeds must collide at **all** ~40 sites to +produce the same world — which is never. + +**Scope:** ~40 call sites, mechanical, plus the same bug now inherited by the operator stack +(`FSlabVoidSource::SeedF`, `VoxelDensityOpStack.cpp`). Must land in **both** paths in one change, or +the equivalence tests will (correctly) scream. **Cost of delay: every archetype ported copies it +again.** + +--- + ### C2 — Per-chunk parameter caches have no layout key (stale after live-edit) ⚠️ **real, reproducible** `VoxelGenerator.cpp:503-524`: @@ -247,7 +289,22 @@ reachable only with a very large origin room *and* short tunnels. Flagging it be --- -### C9 — The build uses `/fp:fast`, and the MP plan assumes bit-reproducible terrain ⚠️ **added 2026-07-27, measured not assumed** +### C9 — The build uses `/fp:fast`, and the MP plan assumes bit-reproducible terrain 🔴 **NOW THE TOP OPEN RISK — promoted 2026-07-27** + +> **Why this outranks everything else in this document now.** Jahni stated the actual acceptance bar: +> *"only having it 99.99% at worst reproducible if two people share the same seed, since everyone +> rebuilds it on multiplayer."* That is **precisely and only** the guarantee this entry says +> `/fp:fast` weakens. Fidelity to the old world was dropped as a requirement (`§C10`); peer agreement +> replaced it as the single bar. **This entry is now load-bearing.** +> +> **The concrete case to decide:** a **Linux dedicated server** generating collision geometry and nav +> against **Windows clients** compiles the same density code under *opposite* float models +> (`ClangToolChain` → precise, `VCToolChain` → `/fp:fast`). That is not a theoretical divergence. +> Same-platform peers on the same build are fine. +> +> **The fix is known and blocked:** `FPSemantics = Precise` on this module, which needs the IWYU debt +> cleared first (seven headers compile only via the shared PCH). That debt is now worth clearing on +> *this* justification rather than on tidiness. Found while chasing a 1-ULP difference between `GetMazeDensity` and its operator-stack port. The difference survived a full bisect down to code that is character-for-character identical, which @@ -332,7 +389,16 @@ risk is real. That is one build, and it settles it. --- -### C10 — The op-stack ULP residue: PARKED, with the evidence, 2026-07-27 +### C10 — The op-stack ULP residue: ~~PARKED~~ **CLOSED PERMANENTLY**, 2026-07-27 + +> **⛔ CLOSED, and now provably irrelevant — not merely parked.** Jahni, 2026-07-27: *"I do not need +> your work to be identical or near identical to what I had before, only having it 99.99% at worst +> reproducible if two people share the same seed."* This entry measures **old path vs new path** +> agreement, and the two paths will never both exist in a shipped world. **No requirement anywhere +> in the project depends on this number.** Do not spend another minute on it. +> +> What survives is the *method* lesson at the bottom of this entry, and the redirect: the +> reproducibility that IS required is **`§C9`** — across binaries and platforms, not across refactors. **Status: accepted and closed by decision (Jahni), not by explanation.** Do not reopen this without reading the whole entry — five hypotheses have already been measured and refuted, and re-deriving diff --git a/OPSTACK-PLAN.md b/OPSTACK-PLAN.md index 7909e87..2c4b856 100644 --- a/OPSTACK-PLAN.md +++ b/OPSTACK-PLAN.md @@ -220,6 +220,38 @@ SIMD noise switch required. --- +### 2.6.1 ⚠️ RELAXED FURTHER, 2026-07-27 — resemblance to the old world is NOT a requirement at all + +**Jahni, verbatim:** *"I do not need your work to be identical or near identical to what I had +before, only having it 99.99% at worst reproducible if two people share the same seed, since +everyone rebuilds it on multiplayer."* + +**This replaces the "recognisably the same place" bar above.** The requirement is not fidelity to the +past — it is **agreement between peers in the present**. Restated as the only two properties that +now matter: + +| Property | Required? | Enforced by | +|---|---|---| +| **Same seed ⇒ same world, on every peer** | **YES — this is the whole bar** | `DensityPurity` within a binary; **`§C9`** across binaries/platforms | +| Resemblance to the pre-refactor world | **NO** | nothing; freely re-tunable | +| Bit-identity with the archetype `switch` | **NO** | nothing; never compare them | + +**What this changes, concretely:** + +1. **`§C10` is closed, not parked.** It measures old-path vs new-path agreement, and the two paths + will never both exist in a shipped world. The residue cannot affect anything Jahni requires. +2. **The equivalence tests keep their value, but for a different reason.** They are no longer + *fidelity* checks; they are **port-correctness** checks — a transcription slip is still a real + bug, and comparing against the old function is the cheapest way to catch one. Read them that way. + The hard-fail (isosurface crossing) stays; the ULP grading is now diagnostic only. +3. **`§C9` is promoted from a footnote to THE risk.** "Two people share a seed" is exactly the + guarantee `/fp:fast` weakens across toolchains, and a Linux dedicated server generating collision + geometry against Windows clients is the concrete case. +4. **Changes that re-roll the world's noise are no longer expensive.** `§C1` in particular was + deferred *only* because it forces a re-tune. That objection is gone. + +--- + ## 3. The contract ```cpp diff --git a/OPSTACK-PROGRESS.md b/OPSTACK-PROGRESS.md index 6279b4e..779550d 100644 --- a/OPSTACK-PROGRESS.md +++ b/OPSTACK-PROGRESS.md @@ -1059,3 +1059,63 @@ sub-ULP-of-scale deltas. Then `SurfaceWorld` (§5) — biggest payoff, most care and the exact-lattice `ClassifyTile` bound must both survive the port. --- + +## 2026-07-27 — the acceptance bar changed, and it invalidated the audit's own fix for C1. + +**Jahni:** *"I do not need your work to be identical or near identical to what I had before, only +having it 99.99% at worst reproducible if two people share the same seed, since everyone rebuilds it +on multiplayer."* + +The bar is **peer agreement in the present**, not fidelity to the past. Recorded as +`OPSTACK-PLAN §2.6.1`, which supersedes §2.6's "recognisably the same place". + +**Four consequences, all recorded where they will be found:** + +1. **`§C10` closed permanently, not parked.** It measures old-path vs new-path agreement, and the + two paths never coexist in a shipped world. No requirement depends on it. +2. **The equivalence tests keep their value for a different reason** — they are **port-correctness** + checks (a transcription slip is a real bug), not fidelity checks. Hard-fail on isosurface + crossings stays; the ULP grading is now diagnostic only. +3. **`§C9` promoted to the top open risk.** "Two people share a seed" is exactly what `/fp:fast` + weakens across toolchains, and a **Linux dedicated server** generating collision/nav against + Windows clients compiles the same density code under *opposite* float models. The fix + (`FPSemantics = Precise`) is blocked behind the IWYU debt, which now has a real justification + rather than a tidiness one. +4. **`§C1` is unblocked** — it was deferred *only* because it re-rolls the world's noise. + +### ⛔ And then C1's documented fix turned out to be wrong + +Before applying the one-liner the audit has carried since it was written, I did its arithmetic: + +``` +proposed: SeedF bounded to 16383, multiplier * 97.7 KEPT + max coord term = 1.6e6 -> ULP = 0.19 -> 9.5x the ~0.02/voxel step +``` + +**It bounds the seed but not the offset, and the multiplier is where the magnitude comes from.** The +fix would have made the bug less catastrophic while leaving it live for mid-range seeds — and, worse, +**closed the ticket**. Cost to discover after applying: one build plus a world re-roll. + +**The real fix is to delete the multipliers.** The `· 7.3f … · 97.7f` factors exist only to +decorrelate the ~40 noise sites from each other; that is a hashing job, and hashing does it better: +`VoxelHash::SeedOffset(Seed, Site)` returns a **site-salted, bounded, final-units** offset. Bounding +`SeedF` alone would also funnel every seed through 16384 offsets *shared by all sites*, so two +colliding seeds would give identical noise everywhere; per-site salting means two seeds must collide +at **all ~40 sites**, which is never. + +**And the op stack has already inherited the bug** — `FSlabVoidSource::SeedF` is a faithful +transcription of `const float SeedF = (float)Seed`. It must be fixed in **both** paths in one change +or the equivalence tests will correctly scream. **Every further archetype port copies it again**, +which is the argument for doing it before `SurfaceWorld` rather than after. + +Corrected in `AUDIT §C1` with the arithmetic, so the wrong fix cannot be applied later from the doc. + +**UNVERIFIED:** nothing new in code this entry — docs only. The C1 fix itself is NOT written; it +needs Jahni's go-ahead on scope (~40 sites, re-rolls every world, one build). + +**Next single action — Jahni's call between two:** +- **`§C1`** (recommended first): ~40 mechanical sites + the op stack, one build, kills the + highest-severity latent bug before more ports copy it. +- **`SurfaceWorld`** (`OPSTACK-DECOMPOSITION §5`): the biggest op-stack payoff, most care. + +---