docs: handoff — take the unattended queue all the way to 8 of 8
Jahni, twice and explicitly: he is fine with unverified work as long as it is committed per group, and he wants the successor working toward full op stack completeness. So the queue no longer stops at stage B. Removed the hard stops on stage C and on flipping the ported list. Two facts make that reasonable rather than reckless, and both are now written down: committing per group IS the safety net (he reverts by sha), and reaching 8 of 8 does not change his world by itself — UsesOperatorStackForChunk still returns false unless a strate has bUseOperatorStack ticked, so the flag flip is inert until he ticks a box. Queue is now B1-B5 (13 detail modifiers), C1 (per-room op override), C2 (Underwater, which §8 establishes has no density difference at all), C3 (both into the ported list = 8 of 8), then the §C2 audit question, the worm amplitude cap, and making ClassifyTile consume ClassifyBox. C1's design is settled here rather than left open, because an unattended run should not be improvising architecture: take §2's option (a), and use the mechanism this codebase already has three instances of — one op owns shared state, others read it through a non-owning pointer (overhang <- column source, ledge <- shaft source). FRoomGraphSource exposes LocalParamsAt(); detail ops read their fields from it. Same resolution as the pit/chimney "fiddliest thing in the decomposition": the difficulty came from assuming each op must own its params. Still off limits: build, editor, push, ticking bUseOperatorStack on an asset, and Phase 3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+82
-33
@@ -112,13 +112,25 @@ by a test that compares it to the original. Only the ~60 lines of glue are trans
|
||||
|
||||
## ⚠️ IF YOU ARE RUNNING UNATTENDED — read this section before starting
|
||||
|
||||
Jahni may start this session and leave, then have you shut the PC down when you're done. If so:
|
||||
Jahni may start this session and leave, then have you shut the PC down when you're done.
|
||||
|
||||
**You cannot build, and nobody will build for you during this run.** Rule #1 still holds absolutely.
|
||||
So everything you write stays unverified until he returns — which is exactly the `AUDIT §P3` pattern
|
||||
("unverified code on top of unverified code") this refactor has dodged seven times. You cannot avoid
|
||||
it while he is away. **What you CAN do is shape the work so a single build afterwards diagnoses it
|
||||
cheaply.** That is the whole job of this section.
|
||||
Everything you write stays unverified until he returns.
|
||||
|
||||
**That is his explicit, repeated decision, not an oversight — do not re-litigate it and do not
|
||||
quietly scale the work down.** Verbatim: *"I'm fine with unverified work, if committed, I can always
|
||||
manually go back and try — I want him to work towards the full op stack completeness."* So the goal
|
||||
of an unattended run is **to finish the transition**, not to stop somewhere safe. Take the queue
|
||||
below as far as it goes.
|
||||
|
||||
Two things make that reasonable rather than reckless, and they are worth knowing:
|
||||
|
||||
1. **Committing per group IS the safety net.** He reverts by sha. That is why the one-commit-per-group
|
||||
rule is the one thing not to bend.
|
||||
2. **Reaching 8 of 8 does not change his world by itself.** `UsesOperatorStackForChunk` returns false
|
||||
unless a strate has `bUseOperatorStack` ticked, so adding `TunnelNetwork` and `Underwater` to the
|
||||
ported list is inert until *he* ticks a box. The flag flip is safe to include; pointing a live
|
||||
world at it stays his call.
|
||||
|
||||
### The rules that replace "build after each step"
|
||||
|
||||
@@ -134,7 +146,7 @@ cheaply.** That is the whole job of this section.
|
||||
4. **Update the test in the same commit as the code it covers**, including its coverage counters. Do
|
||||
not leave "I'll add the test after" — after is a build away.
|
||||
|
||||
### The work queue, in order. Stop at the first hard stop.
|
||||
### The work queue, in order. Run it to the end — **8 of 8 is the finish line.**
|
||||
|
||||
- **B1 — Surface roughness** (`STEP 4b`, ~130 lines). Density-space variant: a *different op* from
|
||||
`FSdfRoughnessMod` (quadratic fade, anti-fill clamp, 4 noise types). See `DECOMPOSITION §1`.
|
||||
@@ -152,38 +164,69 @@ After each group, extend the test: turn that group's amplitudes ON in `EnableTun
|
||||
coverage counter proving the group actually fired (**not** just that its param is non-zero — see the
|
||||
pit/chimney lesson below), and move the op-count assertion.
|
||||
|
||||
**⚠️ The one subtlety that lets B happen before C.** Inside `if (bNearCaveSurface)` the original
|
||||
*shadows* `Params` with `LocalTerrainParams` — a copy with the nearest room's terrain op applied — and
|
||||
**all 13 modifiers read the shadowed copy**. Porting them to read strate-level params is therefore
|
||||
equivalent *only while no room carries a detail-type op*. The test's room pool is Pit/Chimney only,
|
||||
and `ApplyTo(Pit)` writes just the four pit fields, so the two are identical today. **Keep it that
|
||||
way until stage C**: the moment a `Terrace` op enters the pool, the ported group must read the
|
||||
per-room value or the equivalence breaks — and that break is the correct signal, not a bug.
|
||||
(B4's Column op is safe: `STEP 4d` reads `SDFCache.Columns` from the bake, never the shadowed params.)
|
||||
**⚠️ The subtlety that lets B precede C.** Inside `if (bNearCaveSurface)` the original *shadows*
|
||||
`Params` with `LocalTerrainParams` — a copy with the nearest room's terrain op applied — and **all 13
|
||||
modifiers read the shadowed copy**. Porting them against strate-level params is therefore equivalent
|
||||
*only while no room carries a detail-type op*. The test's pool is Pit/Chimney only and `ApplyTo(Pit)`
|
||||
writes just the four pit fields, so the two are identical today. **Keep the pool that way through
|
||||
B1–B5**; C1 is what makes a `Terrace` op in the pool legal. (B4's Column op is safe: `STEP 4d` reads
|
||||
`SDFCache.Columns` from the bake, never the shadowed params.)
|
||||
|
||||
### 🛑 HARD STOPS — do not cross these unattended
|
||||
- **C1 — the per-room op override.** `§2` calls this the piece with "no clean home" and offers two
|
||||
options. **Take option (a), and note that the mechanism is already established in this codebase —
|
||||
do not invent a scoping predicate.** Three ops already read state owned by an upstream op via a
|
||||
non-owning pointer handed over at build time: `FOverhangShelfMod` ← `FSurfaceColumnSource`,
|
||||
`FShaftLedgeMod` ← `FShaftFieldSource`. Do the same here:
|
||||
|
||||
- **Do NOT flip `TunnelNetwork` or `Underwater` on in `UsesOperatorStackForChunk`.** That points the
|
||||
live world at an unverified path. It waits for a green build, always.
|
||||
- **Do NOT start stage C (the per-room op override).** It is the one piece with genuine design
|
||||
freedom (`§2` offers two options and argues for (a)), and design without feedback is where an
|
||||
unattended run does damage. **Instead, if you reach it: write the design up in
|
||||
`OPSTACK-DECOMPOSITION §2` and stop.** A reviewed design is worth more than unreviewed code.
|
||||
- **Do NOT start the worm amplitude cap / numeric intervals.** It changes the fold contract that all
|
||||
13 green tests depend on. It deserves a build cycle of its own.
|
||||
- **Do NOT build, do not launch the editor, do not push.** Unchanged, and not negotiable.
|
||||
`FRoomGraphSource` gains `const FStrateGenerationParams& LocalParamsAt(x, y, z) const`, which
|
||||
reproduces the original exactly — take the voxel's `NearestRoomIdx` (it already computes it), apply
|
||||
that room's `RoomOp` onto a `thread_local` copy of the params, return it. Every detail op holds a
|
||||
`const FRoomGraphSource*` and reads its fields from that copy instead of from its own.
|
||||
|
||||
### If you finish B1–B5 and still have time
|
||||
This is the pit/chimney resolution a second time: the difficulty in `§2` came from assuming each
|
||||
modifier must own its params. Let one op own the shared state and the rest read it, and the problem
|
||||
disappears. **Transcribe the per-voxel copy as-is** — yes, the original copies a ~74-field struct
|
||||
per voxel inside the gate; note it in the log as a perf item and change nothing.
|
||||
|
||||
Prefer work that is **valuable without a build**, in this order:
|
||||
Acceptance: put a `Terrace` op in the test's room pool alongside Pit/Chimney and require
|
||||
bit-identity. That is the check B could not make.
|
||||
- **C2 — `Underwater`.** Nearly free: `§8` establishes there is **no density difference at all** —
|
||||
`GetDensityAt` routes it to `GetDensityWithParams` and `WaterLevelRelative` is consumed by the
|
||||
render-side water system, not by density. So it is the TunnelNetwork stack, reached from a second
|
||||
`case`. Add the wiring case and an equivalence test that samples the `Underwater` slot. If you find
|
||||
a real density difference, **stop and write it down** — that would contradict `§8` and is worth
|
||||
more as a finding than as code.
|
||||
- **C3 — 8 of 8.** Add `TunnelNetwork` and `Underwater` to `UsesOperatorStackForChunk`'s ported list.
|
||||
Update `CODEMAP §3.2d` and its `UsesOperatorStackForChunk` row, tick `OPSTACK-PLAN` Phase 2, and
|
||||
write the closing `OPSTACK-PROGRESS` entry: the archetype `switch` now has a complete operator-stack
|
||||
twin, per-strate opt-in, every one equivalence-tested.
|
||||
|
||||
1. **Settle `AUDIT §C2`'s suspected item** — does `GetGenerationParams` actually vary within one
|
||||
strate across a Gradient transition? That is answerable by *reading* `GetGenerationParams` and
|
||||
`FStrateGenerationParams::Lerp`. Turn the suspicion into a yes or a no, with the evidence, and
|
||||
correct the audit either way. **This is the highest-value build-free task available.**
|
||||
2. **Write the stage-C design** (above) without implementing it.
|
||||
3. **Re-read your own B1–B4 diffs against the original**, function by function, as a reviewer rather
|
||||
than an author. On a run with no tests, a second reading is the only oracle you have.
|
||||
### After 8 of 8 — keep going, in this order
|
||||
|
||||
1. **Settle `AUDIT §C2`'s suspected item.** Does `GetGenerationParams` actually vary within one strate
|
||||
across a Gradient transition? Answerable by *reading* `GetGenerationParams` and
|
||||
`FStrateGenerationParams::Lerp` — no build. Turn the suspicion into a yes or a no with the
|
||||
evidence, and correct the audit either way. Cheap, and it closes an open question honestly.
|
||||
2. **The worm amplitude cap (`DECOMPOSITION §0.2`).** The largest single perf item in the plan, and
|
||||
the reason TunnelNetwork proves 0 of 40 tiles. It needs the fold to carry a **number**, not just a
|
||||
direction: add a numeric carve/fill amplitude alongside `EVoxelOpEffect`, have
|
||||
`FWormFieldSource::MaxCarveAmplitude()` (already written, already unused) feed it, and let
|
||||
`AllSolid` survive when the rock is solid by more than the sum of every remaining carve.
|
||||
**⚠️ This changes the fold contract that all 13 tests rest on — its own commit, nothing else in
|
||||
it,** and `VoxelForge.OpStack.BoxVerdictFold` must be extended in the same commit.
|
||||
3. **Make `ClassifyTile` consume `ClassifyBox`.** Verified per archetype, still unconsumed in
|
||||
production — this is where measured tile-skipping (Maze 23/60, slabs 36–40/60) becomes frames.
|
||||
A false verdict is an invisible hole, so keep the brute-force check in every archetype's test.
|
||||
4. **Re-read your own diffs against the original**, group by group, as a reviewer rather than an
|
||||
author. On a run with no builds, a second reading is the only oracle you have. Do this rather than
|
||||
starting anything new when the queue runs out.
|
||||
|
||||
### Still not yours to do, even unattended
|
||||
|
||||
- **Do NOT build, do not launch the editor, do not push.** Unchanged, not negotiable.
|
||||
- **Do NOT tick `bUseOperatorStack` on any strate asset.** C3 makes the archetypes *available*;
|
||||
pointing a live world at an unverified path stays Jahni's decision.
|
||||
- **Do NOT start Phase 3** (ops as data assets). It is a design conversation, not a transcription.
|
||||
|
||||
### The commit messages ARE the ledger
|
||||
|
||||
@@ -196,12 +239,18 @@ That makes the subject line load-bearing. Name the group and the scope, not the
|
||||
```
|
||||
feat(opstack B2): port Terrace, LayerLines, Ribbing detail modifiers
|
||||
feat(opstack B4): port Columns, Domes, Pinch, FloorBias + column op in the test pool
|
||||
feat(opstack C1): per-room op override — detail ops read LocalParamsAt from the room source
|
||||
feat(opstack C3): TunnelNetwork + Underwater in the ported list — 8 of 8
|
||||
```
|
||||
|
||||
Body: what was transcribed, anything that looked wrong and was ported as-is anyway, and what breaks
|
||||
first if the group is wrong. One group per commit — that rule exists *because* of the revert
|
||||
workflow, so it is the one thing not to bend.
|
||||
|
||||
**Close the run with one `OPSTACK-PROGRESS` entry** listing every commit made, in order, with a
|
||||
one-line "what this touches" each, and the test filter to run (`VoxelForge`). Jahni's first action on
|
||||
return is a single build; that entry is what makes it efficient. Then shut down if he asked you to.
|
||||
|
||||
## Hard rules that prevent real bugs
|
||||
|
||||
- **Density sign:** negative = solid at the mesher. Inside the op stack the convention is INTERNAL
|
||||
|
||||
Reference in New Issue
Block a user