Files
VoxelForge/Docs/archive/WORK-NEXT.md
T
Claude 1de44af7cc chore(repo): archive historical docs under Docs/archive, drop tracked pycache
Move closed audits, task briefs, design rounds, perf reports, the OPSTACK
plan/progress logs and WORK-NEXT.md into Docs/archive/ (git mv, history kept).
Whitelist Docs/ in the strict .gitignore, ignore Tools/**/__pycache__/, and
remove the tracked .pyc. Repoint references in the kept docs and in non-test
source comments to the new paths.
2026-09-28 13:46:17 +00:00

372 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WORK-NEXT — the queue after the read-only audit (2026-09-11)
Ordered by importance, then by size. Everything here comes from Sol's read-only audit of
`experimental` @ `aa62971`, checked against `main`.
The audit's two-sentence verdict:
> *The operator abstraction leaked into the innermost loop, and streaming treats
> "work submitted/applied" as "safe to stand on."*
**Standing rules for every item**: determinism is absolute (same build + same seed = same world on
every machine); output may change between builds; nothing is deleted to go fast; capability before
speed (player-fit 20,830 / walk-reachable 10,909 — do not drop below); scope is
`Plugins\VoxelForge` only, no links, absolute `-out=` paths under the plugin's own `Saved\`.
---
## 1. Collision-ready before player-ready · CORRECTNESS · medium
The player falls through the world. There is **no invariant** that a tile you occupy has collision.
`main` has no such barrier either — it was simply fast enough to hide the race.
- `apply_s` ends at mesh **submission**. RMC cooks collision **asynchronously** and we never record
cook-complete. We have never measured when physics can actually collide.
- A tile enters `LoadedTiles` before its apply path completes (`VoxelWorld.cpp:1532`).
- Only LOD0 carries collision (`VoxelWorld.cpp:3109`), so visible coarse terrain can exist while the
collidable tile is still queued.
**Do**: record cook-complete; gate the pawn (spawn and movement) on the supporting tile being
collidable, not merely applied.
✅ **Owner playtest, 2026-09-13: he can no longer fall through the world.** The gate uses RMC's
`SetCollisionConfig()` future as the authority (`HandleTileCollisionCookComplete`,
`IsPlayerSupportCollisionReady`).
📖 **Reading `Plugins/RealtimeMeshComponent` is permitted for this item — READ ONLY.** Find whether a
cook-complete delegate/flag exists. ⛔ No edits, no builds against it: the owner's copy carries custom
modifications and was destroyed once by an over-broad delete.
## 2. Lower the operator graph into a fused evaluator · PERFORMANCE · large
This is where the ~8× lives (pre-op-stack `34f06df` did 128³ in 0.130 s; we are at ~1.04 s).
A LOD0 tile samples 42,875 positions. The old path short-circuited the whole cave suite per sample
when `bNearCaveSurface` was false — roughly **70% of deep-solid samples**. We now allocate a rich
`FVoxelOpSample` per position, run each active operator across the entire block, use scalar fallbacks
for most operators, copy scratch in and out, and still call scalar `GetDensityAt` per grid point.
> *An operator whose interval intersects the surface anywhere in the block runs across the whole
> block; the old path could decide per sample that expensive detail work was irrelevant.*
**Do**: keep the authored operator graph, but **compile/lower it** into a fused evaluator that restores
per-sample short-circuiting and stops spilling `FVoxelOpSample` through memory per operator.
⚠️ Byte-identical geometry is the acceptance test. Stage it so the tree never breaks.
Note: the D-floors/terracing features are only ~17% of the penalty — not the 8×.
## 3. Cancel obsolete work, and prioritise the floor · BOTH · medium
Nothing aborts a tile when the player moves and it stops being desired. Work is distance-sorted from
an artificial chunk centre, not the pawn, with no collision-critical priority
(`VoxelWorld.cpp:1864`). Up to 14 workers can be busy with stale coarse tiles while the player's LOD0
floor waits.
**Do**: abort no-longer-desired tiles; sort from the true pawn position; give the occupied tile and the
next tile along the heading absolute priority.
## 4. Collapse the duplicated field semantics · PERFORMANCE ARCHITECTURE · medium-large
The effective field now lives in three places: the operator stack, the classifier's parallel interval
logic, and a post-stack repair tail in `GetDensityAt` (origin floors, passage structure, passage
floors, room floors). `FRoomGraphSource` evaluates the room graph then traverses tunnels *again* for
metadata (`VoxelDensityOpStack.cpp:4234`).
The audit calls this *"the clearest sign of overengineering"*: later fixes compensating for where an
operation sits in the pipeline. It also makes interval proofs harder to trust.
**Do**: make the stack the single semantic source of truth. Likely overlaps heavily with item 2.
**Promoted 2026-09-12**: this is performance architecture, not maintenance. The classifier is a
second evaluator (interval proofs, exact certificates, and final-field validation) beside the
canonical lowered evaluator, and the runtime can validate the same tile again before meshing.
Items 1 and 2 in the current round deliberately leave this lowering work untouched; any
classifier retained later must consume the lowered representation and reusable density data.
## 5. Verdict cache removed after moving-session audit · complete
The clean moving-session audit found zero outer or nested hits: 2,109 outer probes and 19,904 nested
probes produced 15,877 stores and 15,365 replacements in the full 512-entry table. Same-binary
cache-on/cache-off request-to-ready was 0.367645/0.365978 s p95 and generation was 0.285559/0.285992
s p95, so the cache was neutral within run noise while paying the global lock and linear scans.
The global verdict cache, lock, entry table, invalidation path, cache-only profiler fields, and
deliberate cache probe are removed. The proof classifier remains; item 4 is still deliberately untouched.
## 6. Three small, sharp ones · tiny
- **The zero-position guard.** Streaming only starts when `GetPlayerPosition()` is not exactly
`FVector::ZeroVector` (`VoxelWorld.cpp:1167`, `:1359`) — a pawn at the world origin is
indistinguishable from "no pawn", and **the owner spawns at (0,0)**.
- **`ApplyMeshToTile` has no success return** (`VoxelWorld.cpp:3064`). If runtime-mesh init fails the
tile stays recorded as loaded and never retries — a permanent hole.
- **Whole-tile uniform verdicts are trusted without exact validation.** Block verdicts get checked
against the full grid when meshing; an outer AllAir/AllSolid verdict has no equivalent safety net.
## 7. Deferred, with reasons
- **Coarse-first LOD refinement** — blocked until coarse tiles can carry collision, or item 1 lands.
- **GPU evaluation** — right hardware, but bit-exactness across vendors/drivers is unproven and
determinism is absolute. Item 2 is the honest prerequisite either way.
- **The origin spawn room** — the owner considers it a design mistake that should not exist. Design
conversation, not a fix.
---
## Outer-classifier A/B — measured 2026-09-12 (nested refinement OFF in every mode)
Clean, diagnostics off, headless game path. Static = 343 LOD0 samples, two runs per mode; moving =
160 m at 8 m/s, 1,274 LOD0 samples. Density calls = validation + mesher.
| mode | ready p95 static | gen p95 static | worker CPU static | density calls static | ready p95 moving | gen p95 moving | density calls moving |
|---|---:|---:|---:|---:|---:|---:|---:|
| 0 no outer classifier | **182.4-182.7 ms** | **124.9-127.0 ms** | **184.9-193.5 s** | **18.05 M** | **198.9 ms** | **129.9 ms** | **57.7 M** |
| 1 current (validate, discard) | 189.7-194.8 ms | 139.1-140.6 ms | 201.2-202.3 s | 21.89 M | 208.0 ms | 143.4 ms | 69.6 M |
| 2 validation-grid reuse | 190.0-194.7 ms | 140.3-141.3 ms | 201.2-201.3 s | 21.89 M | 210.7 ms | 143.3 ms | 69.6 M |
**Verdict: the outer classifier is net-negative on the game path.** Mode 0 is better on every latency
figure and does ~18% fewer density calls. It cannot remove geometry, since it skips nothing.
**Mode 2 cannot win as designed.** It reuses the validation grid only when a uniform verdict is
*disproved* (`VoxelWorld.cpp` ~4012), and validation disproved none: 0 reuse tiles in every run. Mixed
tiles, the ones that actually need a grid, never run validation, so there is nothing to reuse.
**Done 2026-09-13, owner approved:** `GVoxelForgeOuterClassifierMode` now defaults to 0, and mode 2
is deleted along with its reuse grid, its mesher parameter and its counter. Mode 1 is still
reachable with `voxel.OuterClassifierMode=1` as an A/B instrument. Verified:
- export OBJ SHA-256 unchanged (`b3e5f4c3..9b377`)
- a run without the override prints `outer_classifier_mode=0 validation_density_calls=0`
- **841/841 tiles have identical triangle counts between mode 0 and mode 1**. The 394 tiles mode 1
skipped as uniform produce 0 triangles in mode 0, so the strate band cut was not hiding geometry
behind the classifier.
⚠️ **Correction to `f204413`.** Its message says the nested classifier's 3.5x "cannot be reproduced".
That was my measurement error: I passed `-voxel.UseBlockEarlyOut=1` (the STREAMING console variable)
to the export commandlet, which reads its own `-blockearlyout=` argument, so both of my runs had
nested refinement off. Re-run with the right flag: `-blockearlyout=0` 0.553 / 0.572 s,
`-blockearlyout=1` 2.036 / 2.060 s, which is **3.6x**, with byte-identical geometry. The 3.5x was real,
and disabling nested refinement in streaming (`f204413`) was worth it.
## Room-cache work, 2026-09-13 (`2990009`, `2de603c`, `cfad3d1`)
Player-fit memo, then the tile-sized room cache gated to LOD3+ (fused) / LOD4+ (op-stack). Against
`2990009`, static worker CPU is 48 -> 39 s and moving 125 -> 114 s, with identical geometry.
⚠️ **Correction to `cfad3d1`.** Its message says absolute numbers drift because "the machine is in
daytime use." That was my unverified guess, and it is false: the owner had not used the PC all day.
The real signal is systematic. The switch-off baseline came out at 58 s in two rounds hours apart,
against 48 s at `2990009`. So something left ON with `voxel.TileCacheWindow=0` costs ~10 s, and LOD0
generation p50 is ~4% slower than at `2990009` (73 -> 77 ms static). Queued: attribute it with
in-tree switches (first suspect: `voxel.SpatialIndex` auto-on at fused LOD1-2), never by running old
commits.
**Resolved 2026-09-13 (`ab4bbe3` + the switch sweep):** not the spatial index; every `SpatialIndex`
setting is within ~1%. `TileCacheWindow=0` at HEAD is a different configuration from `2990009`, not
the old path. It still thrashes at L3/L4 (p95 ~250 ms), now at 54 s. The DEFAULT beats `2990009` on
every metric: static worker 48 -> 34 s, moving 125 -> 101 s, LOD0 generation p50 73 -> 66 ms static
and 70 -> 62 ms moving. The "LOD0 ~4% slower" worry is gone. `SpatialIndex` now defaults to 1
(marginally best, within noise). Per-tile identity: 0/841 tiles differ against traces recorded
before the always-on LOD0 changes.
**Next LOD0 target, found by the sampler's caller aggregation:** the "unknown" ~13.8% LOD0 leaf is
`ucrtbase.dll!_finite`, the non-inlined CRT call behind `FMath::IsFinite`. Its callers are the tunnel
chain, passage landing/floor checks and `GetDensityAt`. An inline bit test is exactly the same
predicate, so it changes no field.
## State after 2026-09-14 (HEAD `7a972f5`)
**In-game generation since the start of 2026-09-13**, headless game path (static = 841 tiles;
moving = 160 m at 8 m/s):
```
worker CPU static LOD0 generation p50 static
c65b015 classifier off ~184 s ~75 ms
2990009 player-fit memo 48 s 73 ms
cfad3d1 tile cache window (LOD3+) 39 s 77 ms
ab4bbe3 field-exact LOD0 overhead 33 s 66 ms
5489b5e inline exact IsFinite ~31-34 s ~62-66 ms
7a972f5 (current) ~34 s ~66 ms
```
The later rounds' absolute figures come from slightly different harness settings. Compare within a
round.
**Measured, not guessed: what each component costs** (`6ccf371`, remove-one ablation, static worker
saving): tunnel SDF 11.4% (and it owns the LOD0 p95 tail), cave warp 10.3%, passage carving 10.1%,
landing posts 9.9%, structural posts 5.6%, worms 2.4%, everything else <= 2.2%. Ablations change
the field, so these mix computation cost with the cost of the geometry each component creates.
**Tried and not kept:**
- the exact worm block skip: 4% skip rate, neutral, off by default;
- value-noise / lattice worms: no gain, removed;
- exact culling (SmoothMin cutoff, tile candidates, post early-outs): the build was 23% slower even
with every switch off, parked on local branch `wip/exact-cull-20260914`. Only the tunnel-core
ablation gate fix was ported.
**Measurement lessons**, recorded because each cost a round:
- line-level sampler attribution of inlined code is a hint; confirm it with an ablation;
- a switch-off A/B inside a new build cannot see always-on cost; always compare the final default
build against the committed tip's DLL, with a fixed command line;
- launch Unreal serially (it is a GUI exe that returns at once), and reap crash reporters.
**Next candidates:** a cheaper cave warp (field-changing: coarse warp lattice); the tunnel SDF (field-
changing merge of the warped SDF and the world-space core); a real per-tile candidate filter
designed to cost nothing per sample; the cold read-only audit's findings.
## State after 2026-09-15 (HEAD `a668833`)
**Gameplay fixes, from the owner's playtest:**
- `4c8d06f` the player can walk from one strate into the next. `StrateContentCutMinLevel`
defaulted to 0, which content-cut the LOD0 collision tiles, so the collision gate blocked the
passage: a deadlock. Regression test `-voxel.TestStrateCrossing=1`.
- `39c306b` its +29% cost recovered by an exact sealed-rock proof (`TryProveSealedSolidTile`).
- `cf2b2ad` tunnel floors, option B (the owner's choice): no staircases; steep tunnels WIND
(walk-critical, a reserved spanning forest) or get a few big climbable DROPS (only leaf/redundant
edges). Visual acceptance is the owner's in-game check; the renders did not show it.
- `9260775` passage D-floors were missing inside gap chunks (a real bug since `adb34df`), plus
two stale tests corrected.
**Correctness:** `8879045` fixed the cold audit's findings (`AUDIT-COLD-2026-09-14.md`), including
a determinism bug introduced by the tile-cache work (a moved-from operator stack could be evaluated
on a cache miss), plus cache world-identity, lifetimes and input guards.
**Performance since `7ece3ce`** (each measured against the committed tip's DLL, exact field):
`ce4d878` landing/structural reach -12%; `1d824aa` cave warp SIMD batching -4%; `a668833`
tunnel-core tile reach + passage-carving 8^3 reach -1.3 to -2.6%. Static worker time is now ~28 s,
from ~184 s at the start of the push, with the same world and capability.
**Current cost map** (`0208e93`, remove-one, static): tunnel core 20.8%, tunnel SDF 12.6%, passage
carving 9.1%, cave warp 6.7%, structural posts 6.6%, unattributed ~28%. The tunnel core is
near-dominated (~99% of its cycles are in tiles near tunnels), so exact skips cannot remove it. The
next lever is the field-changing SDF/core merge (in progress), which will also add an automation
test for the reach proofs.
**Parked:** `wip/exact-cull-20260914` (per-sample culls, a 23% always-on regression; only its gate
fix was ported).
**CLOSED 2026-09-15: the tunnel SDF/core merge. Do not re-propose it without a new idea.** The
world-space core is load-bearing: `WorldControlPoints` keep the authored room-floor anchors and
walkable routes before cave warp (`8e42d6d`, `cf2b2ad`). Replacing it cost 1,146 fit / 975
walk-reachable cells; the exact shared-segment variant was 5.6-12.6% SLOWER. The remaining big
costs (tunnel core ~20%, tunnel SDF ~13%) are the geometry itself, so the exact-skip perf push has
reached diminishing returns. The reach proofs are now guarded by
`VoxelForge.Correctness.TilePostReachProof`.
## State after 2026-09-16: Sol's design review, deterministic maths (`10fd0b6`)
**Reframe.** Local "find the hot spot" rounds had reached diminishing returns, so, on the owner's
idea, Sol High was asked how it would build the project from scratch (`DESIGN-SOL-2026-09-15.md`),
then read the code and said what could reasonably change (`DESIGN-SOL-2026-09-15-CODE.md`). Its
verdict: don't rebuild. There are two performance bets, each with instrumentation-only first steps
and kill criteria, plus one determinism hole. Owner's rule: each idea gets its own branch and is
dropped if it doesn't hold capability and performance.
**Done: `sol/deterministic-math`, merged.** On Win64, `FMath::Sin/Cos` were CRT `sinf/cosf`, and the
UCRT picks an FMA3 variant by CPU at start-up. Now `VoxelMath::DetSinCos` (fixed reduction and
polynomial, max error 5.96e-8) is used on every world-deciding site; `-voxel.CrtFma3=0` forces the
CRT's other path, and exports are byte-identical either way. Before the fix, the two paths also
matched on this Ryzen, so the hole was real but not observed here. Capability is exact;
static/moving worker time is -3.6%/-3.5% vs the tip DLL. Order-independent vertex distance is at
most 8.7 micrometres (a line-by-line OBJ diff reported "11.4 m", a line-order artefact: compare
meshes by nearest vertex, not by line).
**New canonical hashes (the world changed once, by design):**
- export-only OBJ SHA-256 `CBB8AE185E949B33CF2656441BA4AB17398975A098967870DFD45B435A37906F`
(geometry `B9EF84AC`), was `b3e5f4c3..9b377`;
- worms/capability region `9242C093`, was `311CA42C` (walk OBJ `20CCF7BD..0100`);
- field digest (cross-platform test grid) shape `0x767FB0650936435D`, field `0x2AE570A5A1A9C5CE`;
- capability unchanged: 20,830 / 10,909, connected.
**Rung 1 done (`b003bee`, `ba7a083`, branch `sol/perf-probes`, probes off by default):**
- ⛔ **Bet A (tile-wide feature-major tunnel evaluation) is KILLED. Do not re-propose it without a
new idea.** The per-sample lookup it removes is only 5.1-6.1% exclusive, and feature-major
visiting would touch **5.2-6.6x more lattice points** than the current code evaluates (static
LOD0: 49.3M predicted vs 9.5M expensive evaluations), because the cheap AABB/sphere rejection
(63M of 80M visits) is exactly what it throws away. The swept kernel is branch-divergent
(per-sample segment choice, ownership and support branches), so no low-risk X-lane SIMD.
- ✅ **Bet B (cross-tile density reuse) cleared every gate**, and the orchestrator re-derived it
from the raw trace independently (`scratchpad/dupcheck.py`): static LOD0 20.04% of density calls
are exact duplicates, 80.6% with a donor already finished (~2.96 s of 18.3 s); moving 20.70%,
63.3% (~5.94 s of 45.3 s). Ceiling ~16% static / ~13% moving of worker CPU.
**Rung 2 (`sol/tile-sample-reuse`, WIP `9a67452`): INTERRUPTED by the Codex usage limit**
(2026-09-16 02:07, resets 19 Sep 12:44). The slab cache, its key test, mesher donor views and
`VoxelWorld` plumbing are committed **UNVALIDATED: never compiled, run or measured** (zero Unreal
launches that round). Resume brief: `scratchpad/tile-sample-reuse-resume.md`. Acceptance unchanged:
byte-identical canonical export, capability 20,830 / 10,909, >=6% worker CPU in both scenarios vs
the tip; kill under 4%. Also still owed: a same-session switch-off A/B, because rung 1's probes-off
static (28.18 s) sits above the maths round's (27.41 s) for the same code.
**Then:** landing gap (ARCHITECTURE, passage landings): replace the random-reach fallback with a
deterministic retry.
Smaller hardening noted by Sol, not scheduled: field-wise cache hashing instead of raw-memory
CRC32; an integer-lattice mesher entry point; server-sequenced edit order once networking exists.
## State after 2026-09-17 night: tunnels from the owner's playtest (branch `fix/tunnel-shape`)
**Found in play, not in harness:**
- His editor had been running a 14 Sep build (no crossing fix: `CutMinLevel=0`).
- A gentle slope of 0.1 froze world loading.
- Tunnels were ribbed on floor, walls and ceiling with every terrace setting at 0.
- Tunnels poke into rooms.
The harness uses plugin defaults, not his `DA_Strate3`/`DA_Settings`. **Reproduce owner reports with
his assets staged into `Saved\BuildHost`.**
**Cause of the ribs (`4fa3931`), proven in game by the owner.** `voxel.TunnelAblateTunnelCore=1`
removes them. That switch is read ONCE per process, at the first Play: set it before pressing Play,
and check `[VoxelForgeTunnelAblation] resolved mask`. The tunnel core's final reassertion replaced
interior samples with a fixed +16 air density, so the field lost its distance gradient and marching
cubes snapped the surface to the voxel grid. It now reasserts the continuous `-Core.SDF`. The
periodic amplitude along a tunnel is now under 1.1 cm (was up to 16.9 cm on walls).
- ⛔ The first attempt (`0231072`, a Catmull-Rom centreline) was wrong, cost 18-30% CPU, and is
reverted. It was "verified" by small, unmatched renders. **Use a quantitative instrument**, e.g. the
surface-position oscillation along the axis (`tunnelcoreprobe` mode in the explore commandlet).
**Owner decisions implemented:**
- no authored tunnel floor: `bTunnelFloorEnabled` is ignored and the slab/support-floor paths are
detached;
- slope in degrees (0-90, migrated from tangents, 3840-voxel route cap);
- winding and ledges independent of any floor switch.
Canonical walk: Connected, player-fit 41,184, walk-only 13,879 (was 20,830 / 10,909). One perf pair:
+2.7% static / +4.2% moving vs `bf9384a`, to be re-measured. Not merged: waiting for the owner's
in-game look.
## State after 2026-09-18 night: room wins over tunnels (branch `fix/tunnel-mouths`, `d01af46`)
The owner played the merged rib fix: *"the tunnels do look better now"*, performance on par. Then he
reported a rock shelf spanning a room, ripple rings around tunnel mouths, and tunnels still running
into rooms.
- **Cause of the shelf:** the tunnel core reasserted solid below its own floor **inside room
volumes**, plus the landing-floor post. Measured as tunnel-attributed solid inside rooms:
`469 → 0`. A matched re-render of his room shows the slab gone.
- **Room ownership is now enforced in every tunnel path** (generic tunnel SDF, core evaluator, both
native fallbacks): a room suppresses the tunnel bottom inside itself, while a short fade keeps the
exterior mouth fillet.
- **Mouth trim is now ON by default** (`voxel.TunnelMouthTrim`, dev-only cvar, shipping constant
true). It broke walkability before; it no longer does, because trimmed endpoints take their height
from the room's own vertical zero crossing (join offsets `-2.08/+0.40 → -1.13/-0.71` voxels).
- **Mouth ripples:** periodic amplitude `1.49 cm` floor / `0 cm` wall (degree-six trend removal;
raw local-median `6.19 cm` kept as audit). ⚠️ Instrument lesson: a broad room-floor bowl reads as a
6 cm "period" unless the slow trend is removed first.
- Canonical walk `Connected`, `42,870` fit / `17,413` walk-reachable (was 41,184 / 13,879); export
identical with `-voxel.CrtFma3=0`; `TilePostReachProof` and `DetSinCos` pass.
- Left when the owner reclaimed his PC: strate crossing and the perf pair.
**Harness notes (they cost real rounds):**
- `-run=VoxelForgeExplore` belongs to explore/export/probe launches ONLY. With it, automation tests
and `-game` runs silently run the commandlet instead.
- The staged host's `Content` lost `/Game/Untitled`; the crossing test needs that level (it carries
the `AVoxelWorld` actor), else Unreal fails the map load and crashes on the empty world.
- Codex's headless runs deleted the owner's `%LOCALAPPDATA%\...\EditorSettings.ini` on 17 Sep (a
failed cross-volume move, error 5). Runs now use isolated user/config dirs; verify that file's
hash before and after a round.
**Queued, waiting for the owner's go:**
1. Terracing he can target: rooms / tunnels / terrain, and floors / walls / ceilings (brief ready:
scratchpad `terrace-targets.md`).
2. Tunnel mouths trimmed at room walls (`voxel.TunnelMouthTrim`, default off: 36 m walk gap when on).
3. The landing random-reach fallback.
4. Bet B rung 2 (paused).
### Open design questions for the owner
- All five strate slots resolve to `DA_Strate3`. Intended?
- Should a composer roll be allowed to overwrite an explicitly authored value?