docs: close the operator stack -- handoff rewritten in plain language
Jahni built the last two commits and the game looks fine. Closing the thread. OPSTACK-HANDOFF.md fully rewritten. The old one had become a private dialect -- "you've started using very unique terminology, i have NO idea what you've been doing" -- which is a failure of the document, not the reader. The new version leads with what the op stack was for, says plainly that Phase 3 was never built and is not being built, carries the honest three-week ledger (120 commits, every feat: a port, the world unchanged by design), and translates the jargon. Final state: 8/8 archetypes live and bit-identical, 14/14 tests green through871ca19, violations 0, and 39% of tiles skipped in the running game against 0% that morning. Flagged in the handoff:4d33321and91585eaare built but not re-verified -- the suite has not been re-run since. Two named checks, and revert4d33321if either fails; the 39% does not depend on it. Direction agreed: no Phase 3, no further refactoring, old switch stays as the oracle, next work is visible content from fable-idea.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+77
-313
@@ -1,339 +1,103 @@
|
|||||||
# Handoff — VoxelForge operator stack, updated 2026-08-16 (four things queued on ONE build)
|
# VoxelForge handoff — the operator stack is DONE. Read this, not the old jargon.
|
||||||
|
|
||||||
> Paste the block below into a fresh session. Everything it refers to is on disk and in git.
|
> Paste this into a fresh session. Written 2026-08-16, deliberately in plain language: the previous
|
||||||
>
|
> version of this file had become a private dialect that Jahni could not read, which is a failure of
|
||||||
> **State:** 8 of 8 archetypes ported and green. Tile-skipping is measured **in the automation
|
> the document, not of the reader.
|
||||||
> harness** (11 of 40 tiles proved `AllSolid` at production defaults, 14641 voxels brute-forced,
|
|
||||||
> 0 violations) and **still unobserved in the running game**. `AUDIT §C2` is **fully closed** (both
|
|
||||||
> halves — verified 2026-08-16, don't reopen). `experimental` is pushed.
|
|
||||||
>
|
|
||||||
> ## ⛔ FOUR unbuilt things are stacked on `experimental`. Build once, read four numbers.
|
|
||||||
>
|
|
||||||
> | # | commit | what to read |
|
|
||||||
> |---|---|---|
|
|
||||||
> | 1 | `e002bd4` VerticalShafts connector capsules | `Box verdicts over 60 VerticalShafts tiles` — **0 has been the number for the project's whole life**; `violations` must stay 0 |
|
|
||||||
> | 2 | `eb317d9` `stat VoxelForge`, 8 counters | baseline `TilesOpStackSolid` = 0 → tick one `TunnelNetwork` strate → **non-zero**. That is the production proof of T1.d, which has never existed |
|
|
||||||
> | 3 | `7dbdf51` `ExtraReach` × `VF_PerlinAbsBound` | Maze/VerticalShafts may prove **FEWER** tiles. **That is correct, not a regression** |
|
|
||||||
> | 4 | `eaa44bf` `Max3` radius envelope | **a NO-OP at shipped defaults is the correct result** — any moved number means the diff did more than intended |
|
|
||||||
>
|
|
||||||
> In all four: the eight equivalence tests must stay green, and `violations` must stay 0.
|
|
||||||
>
|
|
||||||
> **⚠️ 3 and 4 are CORRECTNESS fixes to box verdicts, found by auditing all 28 `EffectOverBox`
|
|
||||||
> implementations.** Both were the same mistake: *a bound taken from the parameter that reads like
|
|
||||||
> the maximum instead of the supremum of what `Eval` actually produces* — and both times a correct
|
|
||||||
> instance of the same reasoning already existed elsewhere in the same file. See
|
|
||||||
> `OPSTACK-PROGRESS.md` 2026-08-16 (e) and (f); the sound-and-checked ops are listed there so they
|
|
||||||
> are not re-audited.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
You're picking up the VoxelForge UE5 voxel plugin on branch `experimental` (already checked out —
|
## 1. What the operator stack was, in one paragraph
|
||||||
do not create another). I'm Jahni. The design and the history are written down so you don't
|
|
||||||
re-derive them.
|
|
||||||
|
|
||||||
## Read first, in this order
|
Cave generation used to be one big `switch`: each cave type (tunnels, maze, shafts, floating
|
||||||
|
islands, surface…) was its own hardcoded ~200–1000 line C++ function. The refactor replaced that with
|
||||||
|
small composable pieces ("operators") that stack up to produce the same terrain. **The promise was
|
||||||
|
that you could eventually invent new world types by combining pieces in the editor instead of asking
|
||||||
|
for another thousand-line function.**
|
||||||
|
|
||||||
1. **`CLAUDE.md`** — project rules. **Rule #1 is absolute: never build, compile, or run the editor.**
|
**That promise — "Phase 3", ops as data assets — was never built, and is NOT being built now.**
|
||||||
I build everything myself. When code is done, stop, say "ready to build", list the likely
|
|
||||||
compile-error spots, and wait.
|
|
||||||
2. **`OPSTACK-PROGRESS.md` — THE LAST ENTRY FIRST.** Append-only log; the resume point.
|
|
||||||
3. **`OPSTACK-PLAN.md`** — the plan. **§2.6.1 is the acceptance bar** and supersedes §2.6.
|
|
||||||
4. **`OPSTACK-DECOMPOSITION.md`** — per-archetype breakdown. **§0.2** (the amplitude bound) is now
|
|
||||||
*implemented*, not pending; §2 TunnelNetwork and §8 Underwater are history, not instructions.
|
|
||||||
5. **`AUDIT-2026-07.md`** — **§C2's SDF-cache half is FIXED (2026-07-28)**, its live-edit half
|
|
||||||
(`OC_Chunk` / `BM_Chunk` / `FChunkBiomeCache`) is still open; §C10 is SOLVED, don't reopen;
|
|
||||||
§C9's library half is the top open theoretical risk with 0 measured exposure.
|
|
||||||
6. **`CODEMAP.md`** — navigation. Trust symbol names over line numbers.
|
|
||||||
|
|
||||||
## How we work now — Codex writes, you orchestrate
|
## 2. Status: done. Stop refactoring.
|
||||||
|
|
||||||
From 2026-07-29 this project runs **in tandem with Codex (Model Luna, xHigh)**. **Codex handles most
|
- **8 of 8 archetypes ported**, running in production (`bUseOperatorStack` is ticked on the game's
|
||||||
of the coding; you orchestrate.** Concretely:
|
strate assets), and **bit-for-bit identical** to the old path. Verified by a 115 000-sample field
|
||||||
|
digest plus eight per-archetype equivalence tests.
|
||||||
|
- **14/14 automation tests green, 0 violations anywhere.**
|
||||||
|
- **39 % of tiles are skipped in the running game** (`Tiles Meshed` 1.15 vs `Tiles Classified` 1.88).
|
||||||
|
It was 0 % on the morning of 2026-08-16. Each skipped tile avoids ~43 000 density evaluations plus
|
||||||
|
marching cubes.
|
||||||
|
|
||||||
- You read the code and decide *what* to do; you write **precise specs** Codex executes; you **review
|
**Decision taken 2026-08-16, with Jahni:** the op stack is finished. **Do not start Phase 3. Do not
|
||||||
what comes back against the real code, not against its description**; you own the docs
|
start another refactor.** The old `switch` stays in place as the correctness oracle — deleting it
|
||||||
(`OPSTACK-PROGRESS.md`, `CODEMAP §3`, this file) and the measurements.
|
buys nothing today. Next work should be things Jahni can *see*: see `fable-idea.md` (F7 set-pieces,
|
||||||
- **Hand Codex the INVARIANT, not just the task.** This codebase's traps are invisible in a diff —
|
F9 audio were queued before this started).
|
||||||
density sign, `Identity` meaning `Sdf ≥ T` (below), cache keys needing params + `LayoutVersion`,
|
|
||||||
inserting classes above the anonymous-namespace end marker. A spec that omits these gets code that
|
|
||||||
compiles and deletes collision.
|
|
||||||
- `CODEX-TASK-*.md` at the plugin root are the specs. Each carries a **Why**, the **exact site**, the
|
|
||||||
**invariants**, an **acceptance** section, and **notes for the reviewer**. Write the next one the
|
|
||||||
same way.
|
|
||||||
- Unchanged: **never build** (Jahni does), and a plausible patch is not a verified one until a
|
|
||||||
measurement says so.
|
|
||||||
|
|
||||||
## Where things stand
|
### The honest ledger, so nobody re-litigates it
|
||||||
|
|
||||||
All 8 archetypes have an operator-stack twin, per-strate opt-in, each equivalence-tested **bit for
|
Three weeks, 120 commits, from 2026-07-27. Every `feat:` commit in that window is a *port* of
|
||||||
bit** against its original density function. The `switch` and the stack are two complete,
|
something that already worked. **The world did not change by a single voxel — that was the
|
||||||
interchangeable implementations.
|
acceptance criterion.** What Jahni actually got: the 39 % perf win, two genuine pre-existing bugs
|
||||||
|
found (a use-after-free on every strate-asset edit while streaming, and an under-bounded room
|
||||||
|
collection that could make two multiplayer peers generate different geometry), and a number of fixes
|
||||||
|
to bugs the refactor itself introduced. That is a thin return for three weeks, and it is why the
|
||||||
|
direction changed.
|
||||||
|
|
||||||
Everything sits behind `UVoxelStrateDefinition::bUseOperatorStack`; the ported list lives **only** in
|
## 3. The jargon, translated
|
||||||
`UVoxelStrateManager::UsesOperatorStackForChunk` (all 8). `GetDensityAt` and `ClassifyTile` build the
|
|
||||||
stack through the **same** factory, `VF_BuildOpStackForChunk` — a second copy would be a hole.
|
|
||||||
|
|
||||||
> ### ⚠️⚠️ CORRECTED 2026-08-16 — **THE FLAG IS ON IN THE GAME'S DATA ASSETS.**
|
Almost all of it means one thing: **can we prove a chunk of world is entirely rock or entirely air
|
||||||
>
|
without checking every point in it, so we can skip the expensive work?**
|
||||||
> This section used to say *"No strate asset has the box ticked — that is my call and I still
|
|
||||||
> haven't made it."* **That is false and was believed for a whole session.** Jahni: *"the data assets
|
|
||||||
> in game have the switch on."*
|
|
||||||
>
|
|
||||||
> **Everything downstream of that premise flips:**
|
|
||||||
> - The operator stack is **the production density path**, not a dormant twin. The measured perf
|
|
||||||
> regression is a regression players feel, not a lab result.
|
|
||||||
> - Any unsoundness in an op's `EffectOverBox` is a **live** hole, not a latent one. Two were found
|
|
||||||
> and fixed on 2026-08-16 (`7dbdf51`, `eaa44bf`) and **both of those commit messages say "nothing
|
|
||||||
> in the running game was affected" — that sentence is WRONG, for this reason.** See
|
|
||||||
> `OPSTACK-PROGRESS.md` 2026-08-16 (h) for the corrected severity.
|
|
||||||
> - The T1.d prize is already being collected in-game; `CODEX-TASK-001`'s counters measure how much.
|
|
||||||
>
|
|
||||||
> **Never state the flag's state from memory again — it lives in `.uasset` data, which is not
|
|
||||||
> greppable from here. Ask, or read it in the editor.**
|
|
||||||
|
|
||||||
### ✅ T1.d — the tile-skipping prize — is real and measured **in the harness** (not yet in the game)
|
| term | plain meaning |
|
||||||
|
|
||||||
`FRoomGraphSource::EffectOverBox` answers **spatially**. The result, brute-forced voxel by voxel:
|
|
||||||
|
|
||||||
```
|
|
||||||
[production defaults] 11 of 40 tiles proved AllSolid — 14641 voxels checked, 0 violations
|
|
||||||
[dense fixture] 0 of 40 — correct, and structurally inevitable
|
|
||||||
```
|
|
||||||
|
|
||||||
One function's verdict is inherited by `FSdfConvertOp`, the twelve detail modifiers (via
|
|
||||||
`VF_NoCaveOverBox`) **and** `FWormFieldSource` — fourteen operators from one place. That is what the
|
|
||||||
C1 wiring was built for.
|
|
||||||
|
|
||||||
### ⚠️⚠️ THE ONE INVARIANT THAT CAN DELETE COLLISION — read before touching any op
|
|
||||||
|
|
||||||
**`FRoomGraphSource::EffectOverBox` returning `Identity` now means `Sdf ≥ T`, NOT `Sdf == FLT_MAX`**,
|
|
||||||
where `T = max(3·SDFBlendRadius, WormNetworkRange)`. That is sound only because all three consumers
|
|
||||||
of the SDF channel were read one by one:
|
|
||||||
|
|
||||||
| consumer | threshold |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| `FSdfConvertOp::Eval` | `Sdf >= Blend`, and the tunnel stack passes `MakeSdfCarve(P.SDFBlendRadius, …)` ⇒ **K** |
|
| **T1.d / tile skipping** | that idea. The single biggest perf item in the plan. |
|
||||||
| the twelve modifiers | `VF_NearCaveSurface` ⇒ **3K** |
|
| **box verdict / `ClassifyBox`** | "is this whole box uniform?" → `AllSolid`, `AllAir`, or `Mixed` (don't know) |
|
||||||
| `FWormFieldSource::Eval` | `CaveSDF >= WormNetworkRange` ⇒ **WormNetworkRange** |
|
| **`Mixed`** | "can't prove it" — always safe, just means we do the work |
|
||||||
|
| **`ClassifyTile`** | the function that decides, per tile, whether to skip meshing |
|
||||||
|
| **operator / op stack** | one generation step (rock, carve, roughness…) and the list of them |
|
||||||
|
| **equivalence test** | proof the new path produces byte-identical terrain to the old one |
|
||||||
|
| **`violations`** | ⚠️ **the only number that means danger.** A tile wrongly proved uniform has *no geometry and no collision* — a player falls through the floor. Must always be 0. |
|
||||||
|
|
||||||
**Any new consumer of `InOut.Sdf` must have a threshold ≤ `T`, or be added to that `max`.** An op
|
## 4. What is verified, and what is not
|
||||||
reading `Sdf < 100` would see false `Identity` verdicts and produce tiles with no geometry **and no
|
|
||||||
collision**. The warning is written at the site you land on when you add one.
|
|
||||||
|
|
||||||
(The `−K` slack covers *any* number of primitives because `SmoothMin`'s penalty is exactly zero once
|
**Verified:** everything through commit `871ca19` — tests green, digests unchanged, 39 % measured
|
||||||
`|A−B| ≥ K`, so the running minimum saturates at `K` below the smallest term. Without that
|
in game.
|
||||||
observation the slack would scale with the ~88 tunnels in a cache and the criterion would be dead.)
|
|
||||||
|
|
||||||
## First actions — one build to read, two tasks to hand Codex
|
⚠️ **Built but NOT re-verified:** `4d33321` (Sol's boundary fold — lets a tile that straddles cave and
|
||||||
|
open air still resolve) and `91585ea` (a test-only warning demotion). Jahni built these and says the
|
||||||
|
game *looks* fine, but **the test suite has not been re-run and the counters have not been re-read
|
||||||
|
since.** Before trusting them:
|
||||||
|
|
||||||
### (a) Hand Codex `CODEX-TASK-001-tile-skip-stats.md`, then `-002-` — 001 first, they chain
|
1. run the `VoxelForge` automation filter — **`violations` must be 0 and all eight equivalences
|
||||||
|
bit-identical**;
|
||||||
|
2. `stat VoxelForge` in game — the accounting must close:
|
||||||
|
`Tiles Meshed + Skipped All Air + Skipped All Solid = Tiles Classified`.
|
||||||
|
|
||||||
Everything in this refactor has been proved in an automation harness on 40 sampled tiles, and
|
If either fails, `git revert 4d33321` — the 39 % win does not depend on it.
|
||||||
**nothing has ever been observed in the running game.** Task 001 adds a `stat VoxelForge` group with
|
|
||||||
`TilesClassified / TilesSkippedAllSolid / TilesSkippedAllAir / TilesMeshed`, **plus
|
|
||||||
`TilesOpStackSolid / TilesOpStackAir` at a second site**. Task 002 adds `ColumnMemoHit / Miss` to the
|
|
||||||
same group; it needs 001's header to exist, and both should land in one build.
|
|
||||||
|
|
||||||
Its deliverable is a **before/after that constitutes the production proof of T1.d**: after ticking
|
## 5. Rules that still prevent real bugs
|
||||||
`bUseOperatorStack` on one `TunnelNetwork` strate and flying the same route, **`TilesOpStackSolid`
|
|
||||||
must go non-zero**. The spec carries the invariants — most importantly that `bTrivialEmpty` decides
|
|
||||||
whether a tile has **collision**, and that `GenerateTileResult` runs on **worker threads** so a plain
|
|
||||||
`static int32++` is a data race.
|
|
||||||
|
|
||||||
⚠️ **Corrected 2026-08-16 — the earlier version of this bar was unmeasurable.** It said
|
- **Never build.** Jahni builds; he has the editor open and it costs him real time. Say "ready to
|
||||||
`TilesSkippedAllSolid` would read 0 underground with no strate opted in. It will not: `ClassifyTile`
|
build" and list likely compile-error spots.
|
||||||
also proves `AllSolid` on its **hand-written** path (a bedrock-gap chunk sets `bCanAir = false`,
|
- **`violations` 0 and the eight equivalences bit-identical** — the only non-negotiable results.
|
||||||
`VoxelGenerator.cpp` ~2835), which fires with nothing ticked at all. That is the same "~84 %" caveat
|
- **A bound used to skip work must be PROVED, not observed.** Use `VF_PerlinAbsBound` (= 1.5);
|
||||||
below, which the old bar quoted and then contradicted. The op-stack-only counters are zero **by
|
`FMath::Lerp(A,B,t)` spans `[min(A,B), max(A,B)]`, so a radius envelope is `max(Min,Max)`.
|
||||||
construction** — the cave branch returns `Mixed` at `UsesOperatorStackForChunk` — so they are the
|
- **Never change `ClassifyTile`'s conditions or return values casually.** Every `return` there fails
|
||||||
ones that prove anything.
|
safe to `Mixed`.
|
||||||
|
- **Never state a `.uasset` value from memory** (like `bUseOperatorStack`). Ask, or read it in the
|
||||||
|
editor. This sent a full day sideways.
|
||||||
|
- `FindSlotIndexForChunkZ` is **protected**; `GetStrateChunkZBounds` is the public equivalent.
|
||||||
|
- Push `experimental` freely. **Never push `main`.**
|
||||||
|
|
||||||
Interim answer if Jahni wants it before that lands: **Unreal Insights already shows this.** The trace
|
## 6. Two lessons that generalise beyond this plugin
|
||||||
scopes `VoxelForge_ClassifyTile` and `VoxelForge_GenerateMesh` exist at the site; a skipped tile is a
|
|
||||||
`ClassifyTile` with no `GenerateMesh` after it. ⚠️ But ~84 % of tiles were *already* being rejected by
|
|
||||||
the hand-written SurfaceWorld/bedrock paths long before this work, so surface skips will drown the
|
|
||||||
cave ones — you must be **underground in an opted-in `TunnelNetwork` strate** for the number to mean
|
|
||||||
anything.
|
|
||||||
|
|
||||||
### (b) Build `e002bd4` (VerticalShafts) and read ONE line
|
- **A signal that always says the same thing measures nothing.** A counter that can fire for two
|
||||||
|
reasons is not a measurement — splitting one such counter is what finally cracked T1.d after a day
|
||||||
|
of wrong inference. A warning that fires every run and always means "this is fine" is noise that
|
||||||
|
trains the reader to ignore warnings; one of those quietly worried Jahni for several sessions.
|
||||||
|
- **Verify the premise, and verify it completely.** Multiple confident chains reversed on checking
|
||||||
|
this month. Twice the failure was a *partial* read — grepping a symbol and reporting it as checked
|
||||||
|
for something else. A grep that finds a declaration has not checked its access specifier.
|
||||||
|
|
||||||
Everything before it is built and green.
|
## 7. The open question, which matters more than any of the above
|
||||||
|
|
||||||
> Build, run the `VoxelForge` filter, and read
|
**What do you want the world to *do* that it doesn't?** Three weeks went into a pipeline instead of
|
||||||
> **`Box verdicts over 60 VerticalShafts tiles`**.
|
that question. Start there.
|
||||||
>
|
|
||||||
> **0 was the number for the whole project's life.** Its `EffectOverBox` used to return `CarveOnly`
|
|
||||||
> because a shaft merely *existed* within a `Spacing*1.6` halo — true almost everywhere at
|
|
||||||
> `ShaftSpacing 55 / ShaftDensity 0.6`. It now rebuilds the connectors the way `GetCells` does and
|
|
||||||
> tests the real capsules, with **Z exact** and XY conservative.
|
|
||||||
>
|
|
||||||
> - **Non-zero, and `violations` still 0** ⇒ it worked; record it and move on.
|
|
||||||
> - **Still 0** ⇒ the warning in that test names what to check **first**: `ExtraReach` inflates both
|
|
||||||
> remaining tests, so compare it against `ShaftMaxRadius` before touching either test. **Do not
|
|
||||||
> re-derive from scratch** — that is exactly what cost three rounds on TunnelNetwork.
|
|
||||||
|
|
||||||
## Then, in order
|
|
||||||
|
|
||||||
1. **PERF — the biggest open item, and now AIMED (2026-08-16). Read this before touching it.**
|
|
||||||
The op path is measurably slower; one cause was already found and fixed (the column memo
|
|
||||||
discarded itself every chunk). Three things were worked out since, all still **unmeasured**:
|
|
||||||
|
|
||||||
- **The A/B needs no new code.** `VoxelForge_ClassifyTile` and `VoxelForge_GenerateMesh` already
|
|
||||||
exist, and the world is deterministic, so two Insights traces — flag off, then on, same seed and
|
|
||||||
route — are a clean before/after.
|
|
||||||
- **But it is unreadable without task 001.** With the stack on, tiles get *skipped*, so
|
|
||||||
`GenerateMesh` runs fewer times; a total conflates "cheaper per tile" with "fewer tiles" and
|
|
||||||
those pull opposite ways. `TilesMeshed` is the denominator. **⇒ 001 is a PREREQUISITE here, not
|
|
||||||
a parallel item.** Order: 001 → traces → attribution → fix.
|
|
||||||
- **The suspects don't share an archetype**, so measure one ticked strate at a time.
|
|
||||||
*SurfaceWorld* = the hashed column memo. *TunnelNetwork* = **19 virtual calls per voxel**
|
|
||||||
(16 from `BuildTunnelNetworkStack` + 3 from `AppendStructuralPost`), plus the known 12× gate
|
|
||||||
re-test (stage B5's deliberate trade).
|
|
||||||
|
|
||||||
`CODEX-TASK-002` tests the SurfaceWorld suspect and **fixes nothing on purpose** — the derivation
|
|
||||||
says the direct-mapped 4096-slot table evicts ~25 % of columns *every Z plane* (the mesher
|
|
||||||
pre-samples **Z-outermost**), for a derived ~9× on column work. **Derived, not measured.**
|
|
||||||
**Measure before optimising** — the §C10 lesson, re-learned the hard way last session.
|
|
||||||
2. **The warp squeeze — PARKED with its ceiling measured, my recommendation is leave it.** The
|
|
||||||
`WARP SHARE` line says over half the remaining blocking is the query-box dilation, not geometry
|
|
||||||
(production: rooms 0.9 → 0.4, tunnels 2.4 → 1.1 with the dilation zeroed). The only remaining
|
|
||||||
route is proving `sup|Perlin3D|` down from the proved **1.5** toward its apparent ~1.0–1.1, worth
|
|
||||||
~27 % of the dilation. Spot-checking a grid is **not** a proof and a wrong sup is a hole.
|
|
||||||
**A negative result is already recorded so nobody repeats it:** bounding the warp *locally*
|
|
||||||
(evaluate at the box centre, shift, dilate by the variation) is **worse** — a rigorous per-axis
|
|
||||||
Lipschitz bound is `4·1.875 + 1 = 8.5` per unit cell, and `8.5 × 0.206` (the half-box in noise
|
|
||||||
units) `= 1.75` exceeds the global range bound of 1.5.
|
|
||||||
3. **`AUDIT §C9` library half** — `sinf`/`cosf` are not IEEE-754 specified, so MSVC's CRT and glibc's
|
|
||||||
libm can differ. Currently **0 samples within 1e-6 of the isosurface**, i.e. no measured risk. Run
|
|
||||||
`CrossPlatformDigest` on Linux, compare the SHAPE digest, pin it. The real fix if ever needed is a
|
|
||||||
deterministic in-house sin/cos.
|
|
||||||
4. ~~**`AUDIT §C2`'s remaining half**~~ — **✅ CLOSED, verified 2026-08-16. Do not re-open, and do
|
|
||||||
not spec a fix for it — I nearly did.** `OC_Chunk`, `BM_Chunk` and `TC_BiomeCache` all carry a
|
|
||||||
layout-version guard (`OC_Version` / `BM_Version` / `TC_SeenVersion`), `FChunkBiomeCache` has an
|
|
||||||
explicit `Invalidate()` that all four `thread_local` instances call on a version change, and the
|
|
||||||
only other two instances in the tree are **function-local**, so they cannot go stale. Recorded in
|
|
||||||
`AUDIT-2026-07.md §C2`. The live-edit half was fixed at the same time as the determinism half;
|
|
||||||
only this list was stale.
|
|
||||||
5. **Phase 3 — ops as data assets.** A design conversation, not a transcription. Don't start it
|
|
||||||
unprompted. What makes it possible is already in place: ops depend on capabilities
|
|
||||||
(`IVoxelBiomeField`), never on `UVoxelGenerator`.
|
|
||||||
|
|
||||||
## Debts — status changed, read this before acting on the old text
|
|
||||||
|
|
||||||
1. **"Box bounds read STRATE params but a per-room op can raise them" — DORMANT, not urgent.**
|
|
||||||
Checked rather than paid, and the check reversed the premise: when the source proves `Identity`
|
|
||||||
the twelve modifiers are `Identity` **soundly** (their `bNearCaveSurface` gate never opens, so no
|
|
||||||
room op can enable anything), and when it answers `Both` it supplies no `MaxCarveOverBox`, so the
|
|
||||||
default `FLT_MAX` kills every hypothesis regardless of what the modifiers claim. **It goes live
|
|
||||||
the day `FRoomGraphSource` gains a `MaxCarveOverBox`** — bounding the converter's `2·BaseDensity`
|
|
||||||
would make the modifiers' own numbers matter for the first time. Written at the site.
|
|
||||||
2. **`AUDIT §C2` — FIXED on the `switch` path.** `GetDensityWithParams` now takes **required**
|
|
||||||
`ParamsFingerprint` + `LayoutVersion`. Required, not defaulted, so a caller that forgets fails to
|
|
||||||
compile. The CRC is taken **once per chunk** where the params memo already lives (`CP_TunnelFP`) —
|
|
||||||
a `MemCrc32` per voxel on the hottest path would have been a real regression. Note the audit's own
|
|
||||||
suggested alternative ("add chunk Z to the key") is both insufficient (`Interleaved` makes `Alpha`
|
|
||||||
depend on chunk **XY** too) and destructive (chunk XY is deliberately absent so `WorldX ± 1`
|
|
||||||
gradient probes don't thrash the box — `ARCHITECTURE §8.10`).
|
|
||||||
|
|
||||||
## Hard rules that prevent real bugs
|
|
||||||
|
|
||||||
- **Density sign:** negative = solid at the mesher. Inside the op stack the convention is INTERNAL
|
|
||||||
(**positive = solid**), negated once by the caller. The SDF channel uses standard SDF convention.
|
|
||||||
- **`Identity` from the room source means `Sdf ≥ T`.** See the boxed invariant above. This is the
|
|
||||||
single most dangerous thing in the current code.
|
|
||||||
- **Never run both density paths in one world.** **Comparing them is legitimate** — §C10 is closed
|
|
||||||
since `FPSemantics = Precise`, and all eight equivalence tests compare bit for bit. They are
|
|
||||||
**port-correctness oracles**, not fidelity checks: §2.6.1 requires *same seed ⇒ same world on every
|
|
||||||
peer*, not resemblance to the pre-refactor world.
|
|
||||||
- **Every cache key includes `LayoutVersion` AND the params.** See §C2 and the overhang regression of
|
|
||||||
2026-07-27, where omitting the params silently deleted the overhang and only 1 sample in 20 000
|
|
||||||
crossed the isosurface.
|
|
||||||
- **A bound in a box verdict must be PROVED, not observed.** `|Perlin3D| ≤ 1.5` is derived from
|
|
||||||
`GradDot`'s two-distinct-axes form and the per-axis weighted bound of 0.5 — *not* from the header's
|
|
||||||
"~[-1,1]". Over-estimating costs CPU; under-estimating deletes collision.
|
|
||||||
**⚠️ USE `VF_PerlinAbsBound` — it is file-scope in `VoxelDensityOpStack.cpp` and it is the ONLY
|
|
||||||
copy. Never write a bare `1.0` for a noise amplitude in a reach.** `VoxelNoise::FBM` **normalises**
|
|
||||||
(`return Total / MaxValue`), so `sup|FBM| = sup|Perlin3D|` **exactly** — the octave sum neither
|
|
||||||
amplifies nor attenuates it, and an `FBM`-driven reach needs the same 1.5. This rule was written
|
|
||||||
*before* three `ExtraReach` formulas were found violating it (2026-08-16, fixed in `7dbdf51`):
|
|
||||||
VerticalShafts and Maze were unsound at their shipped defaults, FloatingIslands sound only because
|
|
||||||
its `SDFBlendRadius` happens to be large. **A rule stated in a doc is not a rule enforced in code** —
|
|
||||||
when you add a reach, grep for `VF_PerlinAbsBound` and use it.
|
|
||||||
- `ProcessQueue` stays `EQueueMode::Mpsc`; `Epoch` carries through every async path; don't "optimize"
|
|
||||||
the `ARCHITECTURE §8.10` invariants.
|
|
||||||
- Commit per coherent unit with a real message. **`experimental` is pushed and tracked
|
|
||||||
(`origin/experimental`, since 2026-07-29) — keep it in sync. NEVER push `main`**, which stays the
|
|
||||||
known-good fallback at the commit it has always been. ⚠️ A pushed commit here is **not** a
|
|
||||||
"verified green" marker: the branch carries unbuilt work by design, and only `OPSTACK-PROGRESS.md`
|
|
||||||
says what was actually built.
|
|
||||||
- Update `CODEMAP §3`, `ARCHITECTURE §8`, tick `OPSTACK-PLAN`, append to `OPSTACK-PROGRESS.md`.
|
|
||||||
- **When inserting a class into `VoxelDensityOpStack.cpp` / `VoxelHeightOpStack.cpp`, put it ABOVE
|
|
||||||
the labelled end of the anonymous namespace.** Anchoring on the FACTORIES banner puts it outside,
|
|
||||||
and the brace added with it closes nothing. Made that mistake twice; both files say so.
|
|
||||||
- **Match the codebase's spelling of engine macros.** `KINDA_SMALL_NUMBER`, not
|
|
||||||
`UE_KINDA_SMALL_NUMBER` — the plugin uses the unprefixed form everywhere.
|
|
||||||
|
|
||||||
## Method lessons this refactor actually paid for
|
|
||||||
|
|
||||||
Ordered by how much they cost.
|
|
||||||
|
|
||||||
- **⭐ Instrument what you ASSUMED, not just what you changed.** This is the expensive one, learned
|
|
||||||
over four rounds in one session. The warp dilation — `CaveWarpStrength · VOXEL_NOISE_SCALE ·
|
|
||||||
PerlinAbsBound`, a constant chosen in the first commit — inflated a 10-voxel tile into a 50-voxel
|
|
||||||
query box, **125× the volume**. Four separate tightenings (the worm, the columns, the sampler, the
|
|
||||||
tunnel disjunction) were each individually correct and each landed *around* that untouched term.
|
|
||||||
The tunnel fix, predicted "an order of magnitude", delivered 25 % — **and the instrument said so,
|
|
||||||
and I credited the tunnels.** *When a fix under-delivers against its predicted size, suspect the
|
|
||||||
constant you never measured.*
|
|
||||||
- **Instrument before hypothesising.** §C10 cost six builds and five refuted hypotheses. In this
|
|
||||||
session the attribution line (`AllSolid killed by: …`) was written after *two* wrong guesses and
|
|
||||||
immediately named a third operator nobody had looked at. **A diagnostic that lists candidate causes
|
|
||||||
without measuring them is still a guess wearing rigour** — my "either the tiles straddle cave or
|
|
||||||
the source isn't reaching Identity" warning offered two causes and both were wrong.
|
|
||||||
- **Verify the premise before reasoning from it.** Six times now a confident chain rested on an
|
|
||||||
unchecked assumption and the check reversed it. Latest three: `RoomSpacing` was **42** (the fixture
|
|
||||||
overrides it) while I did three rounds of arithmetic with the header default of 80 — *the number
|
|
||||||
was printing in the report I kept quoting*; "the plugin bets on `|Perlin3D| ≤ 0.8`" was wrong (the
|
|
||||||
cache **rebuilds** when the warped query leaves the box, so that expansion is a perf heuristic);
|
|
||||||
and the per-room-op debt "must be paid first" was wrong (it is dormant). **Include the premises you
|
|
||||||
are confident enough about not to look up — especially a default, when a fixture exists whose whole
|
|
||||||
job is overriding defaults.**
|
|
||||||
- **A sampler must cover at least one period of what it samples.** The tunnel test drew tile XY from
|
|
||||||
**±32 voxels** with `RoomSpacing 80` and a guaranteed origin room at (0,0) — it measured the spine
|
|
||||||
hub and called it the world. The shaft test had the identical bug (±48 against `ShaftSpacing 55`).
|
|
||||||
Both now print their own extent **in units of the pattern's period**.
|
|
||||||
- **A test fixture tuned for coverage can be antagonistic to the thing you are measuring.**
|
|
||||||
`EnableTunnelFeatures` densifies (`RoomSpacing` 80→42, `RoomDensity` 0.35→0.85) so the equivalence
|
|
||||||
check isn't comparing solid rock to solid rock — and at that density the room cull radius *equals*
|
|
||||||
the lattice spacing, so **no box can ever be proved**. `0 proved` there is the correct answer. The
|
|
||||||
box verdict is therefore measured on **both** densities, and the dense run must stay at 0.
|
|
||||||
- **Diagnostics report THIS run; history goes in the log.** The test output had accumulated hardcoded
|
|
||||||
numbers from previous runs beside live ones ("32 of 34 tiles" printed while the live figure was 21
|
|
||||||
of 28). Unreadable, and self-inflicted.
|
|
||||||
- **Read the code, not the comment.** The cliff modifier's comment promises a sampled Z±1 gradient;
|
|
||||||
the code samples nothing and uses a Z-stretched Perlin it *calls* `VertGrad`. Ported as written —
|
|
||||||
and written down, so nobody "fixes" it from the comment.
|
|
||||||
- **A perf change can be a correctness change.** The column-memo optimisation silently deleted the
|
|
||||||
overhang; the tests caught it the same day. Invisible to inspection, and it produced plausible
|
|
||||||
terrain.
|
|
||||||
- **Coverage is a number, not a boolean.** Four related traps, each producing a green run that proved
|
|
||||||
almost nothing:
|
|
||||||
- *A test that prints nothing on success is indistinguishable from one that never ran.*
|
|
||||||
- *A guard that only trips at zero notices absence, it does not measure coverage.* Use fractions.
|
|
||||||
- *A success message that **asserts** coverage instead of reporting it reads as evidence while
|
|
||||||
measuring nothing.*
|
|
||||||
- *A check can be vacuous as well as a counter.* "Nothing leaked" is worthless unless something
|
|
||||||
happened.
|
|
||||||
- **Enabling a feature is not evidence it fired — ask the structure, not the output.** Setting
|
|
||||||
`PitDensity` did nothing (wrong struct). **Prefer the check that can fail for exactly one reason**
|
|
||||||
— and when a zero has several possible causes, give each one its own number.
|
|
||||||
- **An oracle that shares the defect under test proves nothing.** The stale-cache check compares each
|
|
||||||
stack against *itself evaluated alone*. (Since §C2 was fixed, the test call sites now pass a real
|
|
||||||
params fingerprint, so the original no longer shares the defect either.)
|
|
||||||
- **One definition, not two kept in sync.** `VF_BuildOpStackForChunk` exists because a tile skipped on
|
|
||||||
the verdict of a stack that is not the one producing its density is a hole. The same reasoning is
|
|
||||||
why `GetLastRoomBoxDiagnostic` **reads back** what the operator computed instead of letting the
|
|
||||||
test re-derive the criterion, and why the two-density tile scan is one lambda called twice.
|
|
||||||
- **Don't assert a number you want to improve.** Check 4 asserted `0 proved` — honest when written,
|
|
||||||
and it would have forbidden the entire T1.d gain. What it asserts now is that **no proved tile is
|
|
||||||
wrong** (brute force, every voxel); the proved count is *reported*.
|
|
||||||
|
|||||||
@@ -4566,3 +4566,35 @@ same information, but the severity now carries meaning.
|
|||||||
⇒ **General rule for this project's diagnostics:** if a line cannot distinguish a good run from a bad
|
⇒ **General rule for this project's diagnostics:** if a line cannot distinguish a good run from a bad
|
||||||
one, it must not be logged at a severity that implies something is wrong. The same defect as the
|
one, it must not be logged at a severity that implies something is wrong. The same defect as the
|
||||||
lumped bail counter, one layer out: *a signal that always says the same thing measures nothing.*
|
lumped bail counter, one layer out: *a signal that always says the same thing measures nothing.*
|
||||||
|
|
||||||
|
## 2026-08-16 (w) — CLOSED. The operator stack is declared done; direction changes to visible work.
|
||||||
|
|
||||||
|
Jahni built `4d33321` + `91585ea` and reports the game looks fine. He asked for the handoff to be
|
||||||
|
updated and the thread closed.
|
||||||
|
|
||||||
|
**`OPSTACK-HANDOFF.md` fully rewritten in plain language.** The previous version had become a private
|
||||||
|
dialect — his words: *"you've started using very unique terminology, i have NO idea what you've been
|
||||||
|
doing."* That is a failure of the document, not of the reader. The new one leads with what the op
|
||||||
|
stack was for, states plainly that Phase 3 was never built and is not being built, carries the honest
|
||||||
|
three-week ledger, and includes a translation table for the jargon (`T1.d`, `box verdict`, `Mixed`,
|
||||||
|
`violations`).
|
||||||
|
|
||||||
|
### Final state
|
||||||
|
|
||||||
|
- 8/8 archetypes live and bit-identical (115 000-sample field digest unchanged, eight equivalences).
|
||||||
|
- 14/14 tests green through `871ca19`, `violations` 0 everywhere.
|
||||||
|
- **39 % of tiles skipped in the running game**, from 0 % that morning.
|
||||||
|
|
||||||
|
⚠️ **`4d33321` (boundary fold) and `91585ea` (warning demotion) are built but NOT re-verified** — the
|
||||||
|
suite has not been re-run and the counters not re-read since. Recorded in the handoff with the exact
|
||||||
|
two checks and the instruction to `git revert 4d33321` if either fails; the 39 % does not depend on
|
||||||
|
it.
|
||||||
|
|
||||||
|
### Direction, agreed
|
||||||
|
|
||||||
|
**No Phase 3. No further refactoring.** The old `switch` stays as the oracle. Next work is whatever
|
||||||
|
Jahni can see — `fable-idea.md`, F7 set-pieces, F9 audio. The handoff closes on the question that
|
||||||
|
should have been asked in July: *what do you want the world to do that it doesn't?*
|
||||||
|
|
||||||
|
This log stays append-only and stays the record of what was actually built. It ends here for the
|
||||||
|
operator stack.
|
||||||
|
|||||||
Reference in New Issue
Block a user