Files
VoxelForge/OPSTACK-HANDOFF.md
T
Fr0zka f5d5a03ad1 docs: end-of-day handoff — 5 of 8 ported, 11 tests green
Rewrites OPSTACK-HANDOFF.md for a fresh context: the port table, what is left in
order, the two things Phase 2 invented that were not in the original design (height
space as a second operator family, and IVoxelBiomeField so ops depend on a capability
rather than the generator), and the method lessons that cost build cycles to learn.

Open items, none blocking: perf (parked by Jahni), C9's library half (no measured
risk), VerticalShafts' pessimistic box verdicts, and ClassifyTile still using
hand-written guards while ClassifyBox sits verified but unconsumed — which is where
the measured tile-skipping would actually become frames.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:17:35 +02:00

7.0 KiB

Handoff — VoxelForge operator stack, 2026-07-27 (end of day 2)

Paste the block below into a fresh session. Everything it refers to is on disk and in git.


You're picking up an agreed refactor of the VoxelForge UE5 voxel plugin, on branch experimental (already checked out — do not create another). I'm Jahni. Design and progress are written down so you don't re-derive them.

Read first, in this order

  1. CLAUDE.md — project rules. Rule #1 is absolute: never build, compile, or run the editor. 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 of what is built vs merely written. This is the resume point.
  3. OPSTACK-PLAN.md — the plan. §2.6.1 is the current acceptance bar and supersedes §2.6.
  4. OPSTACK-DECOMPOSITION.md — all 8 archetypes broken into ops. §5 SurfaceWorld, §6 shafts, §7 islands, §2 TunnelNetwork.
  5. AUDIT-2026-07.md — §C9 is the top open risk (half fixed). §C10 is SOLVED — don't reopen.
  6. CODEMAP.md — navigation. Trust symbol names over line numbers.

Where things stand — 5 of 8 archetypes ported, 11 tests green

Archetype State
Maze ported, bit-identical, wired
FlatPlain + CrystalChamber one op for both, bit-identical, wired
SurfaceWorld ported incl. biome blending, bit-identical, wired
VerticalShafts ported, bit-identical, wired — 3 ops reused from Maze unchanged
FloatingIslands next (§7)
Underwater (§8 — TunnelNetwork + a water flag)
TunnelNetwork LAST, deliberately — owns BuildChunkCache's two-region window-invariance discipline (§8.4), the most delicate code in the plugin

Everything is behind UVoxelStrateDefinition::bUseOperatorStack; the ported list lives only in UVoxelStrateManager::UsesOperatorStackForChunk. Un-ported archetypes ignore the flag, so ticking it anywhere is harmless.

Two things Phase 2 invented that were not in the original design

  1. Height space (VoxelHeightOp.h) — a second operator family. SurfaceWorld's terrain ops (cliff/terrace/layer-lines/beach) read and write an altitude, not a density: no input Z, XY-pure per column, neither density nor SDF touched. They do not fit IVoxelDensityOp. §0.1 found density needed a second channel; this found terrain needs a second space. Bonus: a height stack cannot hold Z-dependent data because there is no Z in the signature — AUDIT §6.3's hazard became a type error instead of a convention.
  2. IVoxelBiomeField — ops depend on a capability, never on UVoxelGenerator. The adapter (FGeneratorBiomeField) lives in VoxelGenerator.cpp. This is what lets ops become assets in Phase 3; an op holding a generator pointer never could.

What is left, in the order I'd do it

  1. FloatingIslands (§7) — next port.
  2. Underwater (§8) — TunnelNetwork + a flag; fold in once TunnelNetwork is done.
  3. TunnelNetwork (§2) — last, and take §8.4's window-invariance discipline seriously.
  4. PERF — deliberately parked by Jahni until the transition is complete. Generation is measurably slower on the op path. One cause found and fixed (the column memo was discarding itself every chunk). Remaining suspects in order: the hashed column lookup vs. GSurfColCache's direct-indexed box, then per-voxel virtual dispatch. Measure before optimising — that is the C10 lesson.
  5. AUDIT §C9 library halfFPSemantics = Precise fixed the compiler half; sinf/cosf are not IEEE-754 specified, so MSVC's CRT and glibc's libm can still differ. Currently 0 samples within 1e-6 of the isosurface, i.e. no measured risk. If it ever must be zero by construction, the fix is a deterministic in-house sin/cos, not a build flag. Also: run CrossPlatformDigest on Linux and compare the SHAPE digest, then pin it.
  6. VerticalShafts proves 0 of 60 tiles (Maze 23, slabs 36-40). Pessimistic, not wrong: its EffectOverBox returns CarveOnly whenever any shaft is within a Spacing*1.6 halo instead of testing real connector capsules. Lost CPU, never a hole.
  7. ClassifyTile still uses hand-written guards. ClassifyBox exists and is brute-force verified per archetype, but nothing consumes it in production yet. That is where the measured tile-skipping (23 / 36 / 40 of 60, against today's zero) actually turns into frames — arguably the biggest single win still on the table.

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.
  • Never run both density paths in one world.
  • The acceptance bar is §2.6.1: same seed ⇒ same world on every peer. Resemblance to the pre-refactor world is not required. The equivalence tests are port-correctness oracles, not fidelity checks — keep them for that reason.
  • Every cache key includes LayoutVersion AND the params. See the §C2 note 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.
  • 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. Never push. main is the known-good fallback.
  • Update CODEMAP §3, ARCHITECTURE §8, tick OPSTACK-PLAN, append to OPSTACK-PROGRESS.md.

Method lessons this refactor actually paid for

  • Instrument before hypothesising. §C10 cost six builds and five refuted hypotheses, then was solved for free by a build setting changed for an unrelated reason. Park a question whose consequences are measured and benign.
  • Verify the premise before reasoning from it. Twice, a confident chain rested on an unchecked assumption, and twice the check reversed the answer — C1's documented fix was wrong, and "C9's risk is gone after FPSemantics" was wrong.
  • A test that prints nothing on success is indistinguishable from one that never ran. Report coverage counts, not just failures. This bit three times before it stuck.
  • A perf change can be a correctness change. The column-memo optimisation silently deleted the overhang; the test suite caught it the same day. This is the concrete answer to "why all these tests" — the failure was invisible to inspection and produced plausible terrain.
  • 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 now say so at the exact line.