Files
VoxelForge/OPSTACK-PROMPT.md
T
Fr0zka e4d1fdd7ea docs(opstack): experimental is pushed and tracked -- correct the flat "never push" rule
The rule said "Never push" without qualification, which was true while
experimental existed only locally and became misleading the moment it did not:
a future session would read it and let origin/experimental drift.

Now: push experimental freely (tracked since 2026-07-29), never push main, which
stays pinned at the known-good commit. Both statements of the rule are updated
(OPSTACK-HANDOFF.md and OPSTACK-PROMPT.md's crash-safe discipline).

Also added at both sites, because it is the failure mode this creates: a pushed
commit is NOT a "verified green" marker. This branch carries unbuilt work by
design, so OPSTACK-PROGRESS.md remains the only record of what was actually
built and measured, and the remote records only what was written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 14:41:00 +02:00

18 KiB
Raw Blame History

Kickoff prompt — density operator stack refactor

Paste the block below into a fresh context. Everything above the line is for Jahni, not the new session.

Branch: already created and checked out — experimental (from 69fa73e tmp). main is untouched.

Why this exists: the refactor is too big for one context. This prompt makes any fresh session able to start, or resume at a phase boundary, without re-deriving the design.

Working rhythm it enforces: one large batch of code → stop → "ready to build" + likely compile-error spots → Jahni builds → he pastes errors/screenshots → fix → next batch. That is deliberate; see §Autonomy.


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. This was designed with a previous context
and written down so you don't have to re-derive it.

GOAL
Replace the hardcoded archetype `switch` in `UVoxelGenerator::GetDensityAt` with a composable
density OPERATOR STACK, so new world ideas become data-authoring instead of C++. Long-term ambition:
"a world generator of any kind, of any possibility — any combination of ideas could be happening."

READ FIRST, IN THIS ORDER (do not skip, do not skim §2.5)
1. CLAUDE.md            — project rules. Rule #1 is absolute.
2. OPSTACK-PLAN.md      — THE PLAN. §0 the summary, §2 why step 1 is small, **§2.5 the op taxonomy**,
                          §2.6 the acceptance bar, §4 the phases, §5 the invariants, §7 non-goals.
3. AUDIT-2026-07.md     — §6 (the 3D hazards) and §5 (priority list). §1 has real open bugs.
4. CODEMAP.md           — navigation. Trust symbol names over line numbers; the lines are stale.
5. ARCHITECTURE.md §8.10 — the perf invariants. Read before touching any hot path.

THE THREE WAYS YOU WILL FAIL — internalise these before writing code

(1) BUILDING. Never run a build, compile, or the editor. Jahni builds everything himself; he has the
    editor open and running it yourself just burns cost and tells you nothing. When a batch of code is
    done: STOP, say "ready to build", and list the likely compile-error spots. Then WAIT. Same for
    in-editor verification — ask for a screenshot, don't try to produce one.

(2) WRAPPING INSTEAD OF DECOMPOSING. Read OPSTACK-PLAN §2.5. Jahni already had a "room operations"
    system — ops that perturb density near an existing surface. His words: "there's a world of
    difference between a grotto strate and an open world strata." If you turn each old density
    function into one monolithic op, you have rebuilt the switch with extra steps and wasted the
    effort. Archetypes must DECOMPOSE into: FIELD SOURCES (role 1 — what makes a grotto a grotto),
    COMBINERS (role 2 — how sources merge; this is what makes ideas compose), DETAIL MODIFIERS
    (role 3 — his old ops, now one role of four), STRUCTURAL POST (role 4 — spine/seal/passage/diff,
    always appended in that order, never author-omittable).
    The test: can you author "open-world surface strate whose mountains contain a room-graph cave
    system, with floating islands in the upper void" with NO C++? If no, you built the wrong thing.

(3) BREAKING AN INVARIANT. OPSTACK-PLAN §5. In particular: window invariance (§8.4 — every op is a
    pure function of world coords + seed); the T1.a column cache is keyed (XY box, StrateKey, Seed)
    with NO ChunkZ and is shared down the whole vertical stack, so XY-pure data only; every cache key
    must include LayoutVersion (see AUDIT C2 — three existing caches get this wrong today);
    ProcessQueue stays EQueueMode::Mpsc; Epoch carries through every async path.

ACCEPTANCE BAR — read OPSTACK-PLAN §2.6 carefully, two properties get confused
- `ValidateDeterminism` = 0 delta: REQUIRED ALWAYS. Same point, different cache window/thread,
  identical float. This is self-consistency, not reproduction.
- Byte-identical to the OLD system's output: NOT required. Jahni: "not a 1/1 replica, but a possible
  very close result to what I have right now, else it won't really matter much." Recognisably the same
  kind of place at a fixed seed, judged on a screenshot. A one-time param re-tune is expected and fine.
  This relaxation is WHY you are allowed to decompose properly instead of wrapping.

WHAT TO DO — first batch (Jahni has standing permission for multiple changes per build)

A. Phase 0.5 from the plan — the three automation tests. There are currently ZERO tests in this
   plugin and nothing machine-checks the many "bit-identical" claims in the docs.
   1. Density purity: sample ~10k points, shuffle query order, re-sample, assert bit-equality. Run it
      across MULTIPLE worker threads (the existing `ValidateDeterminism` button is game-thread only and
      would miss worker-cache divergence — that's how AUDIT C2 hid).
   2. ClassifyTile soundness: for random tiles, if the verdict is AllSolid/AllAir, brute-force the
      lattice and assert every sample agrees. This is the highest-consequence function in the plugin
      and is currently validated only by reasoning; a false verdict is an invisible, collisionless hole.
   3. DiffLayer under contention: N readers + a writer, assert no crash and monotonic version.

B. Phase 1 skeleton — `Public/VoxelDensityOp.h`: `IVoxelDensityOp` (PrepareChunk / Eval /
   EffectOverBox / IsXYPure), `EVoxelOpEffect { CarveOnly, FillOnly, Both, Identity }`, the four role
   tags, `FVoxelOpContext` (carrying LayoutVersion), and the combiner enum. Header + docs only, no
   ports yet. Start with DIRECTION-only effects — no numeric intervals (OPSTACK-PLAN §2 explains why
   this alone reproduces every hand-written ClassifyTile guard, and why it makes step 1 small).

C. Then STOP and hand off for a build. Do not start porting Maze in the same batch.

If (A) fails on the current code you have found a live bug — report it, fix it, don't build on top.

FOUR THINGS AGREED WITH JAHNI 2026-07-27 THAT THE PLAN UNDER-STATES — hold these as goals

(i) THE BIGGEST PERF PRIZE IS TILE-SKIPPING FOR CAVE STRATES, and it is currently zero. Read
    `ClassifyTile`: any chunk that is neither a bedrock gap nor SurfaceWorld hits
    `return EVoxelTileClass::Mixed;  // archétype cave […] pas prouvable en v1`. So TunnelNetwork,
    Maze, VerticalShafts, FloatingIslands, FlatPlain, CrystalChamber and Underwater capture NONE of
    T1.d's win (which was 84% of gens empty, 44% worker CPU). `fable-idea` wanted this from the start
    ("for cave strates, 'no room/tunnel/passage/spine/seal/diff-layer bounds intersect' — all bounding
    data already exists") and it never happened because a bespoke prover per archetype was too much.
    `EffectOverBox` IS the generic mechanism. A room-graph source returning `Identity` when no room or
    tunnel bound reaches the box makes deep bedrock skippable for the first time. Treat this as an
    explicit deliverable of each port, not a side effect.

(ii) COMPILE THE STACK TO A FLAT TAPE, and don't wait for Phase 3 if the profile says otherwise.
    Naive per-voxel virtual dispatch is ~6 ops × ~43k samples/tile ≈ 257k indirect calls ≈ ~1 ms/tile
    of pure overhead — material against current gen cost. In `PrepareChunk`, compile the stack into a
    flat `(opcode, params)` array and run a switch over a small dense opcode set in the inner loop: no
    vtables, params cache-hot, predictable branches. Offsetting win, worth stating: ops that are
    disabled or out-of-scope are ABSENT from the tape, so the ~15 per-voxel `if (Params.X > 0)` gates
    inside `GetDensityWithParams` today become zero cost instead of one always-false branch each.
    Take an Insights capture after Phase 1; if dispatch shows up, do the tape then.

(iii) THE PERF STORY IS "≈ NEUTRAL PLUS ONE REAL WIN", NOT "FASTER". Do not oversell it in docs or
    reports. The reason to do this refactor is composition. Honest expectation: neutral per-voxel after
    the tape, meaningful gain on tiles-never-generated. If a measurement contradicts that, say so.

(iv) SHIP PRESET STACKS so authoring doesn't regress. A simple world today is "pick an enum, fill one
    struct"; after, it's "assemble 3-5 assets in the right order", which is more clicks and a new class
    of mistake (ordering is now semantic). Provide `DA_Stack_ClassicGrotto`, `DA_Stack_OpenWorld` etc.
    as starting points a strate can diverge from. `ECaveGeneratorType` is expected to disappear
    eventually — but only after every archetype is ported; it stays as the fallback path until then.

HOW TO REPORT AT EVERY STOP
- What changed, file by file.
- "Ready to build" + the specific spots likely to error (signatures, UHT, includes, template/lambda
  capture) so Jahni knows where to look.
- What he should LOOK AT in-editor afterwards, concretely, and what a pass vs fail looks like.
- What the next batch will be.
- Anything you became unsure about. Ask rather than assume — for UE API behaviour especially, ask him
  for the docs instead of guessing (DivideAndRoundDown truncating rather than flooring already cost
  build cycles once).

DISCIPLINE
- Update CODEMAP §3 rows for any new/renamed symbol; ARCHITECTURE §8 for design changes; tick phases
  in OPSTACK-PLAN.md as they land. Comments are French + English — match the surrounding file.
- Never edit Binaries/, Intermediate/, *.generated.h.
- Density sign: NEGATIVE = solid, POSITIVE = air at the mesher. #1 source of confusion.
- Commit per feature with a real message (this branch exists so you can commit freely; `main` is the
  known-good fallback). Do not push.
- If the plan turns out to be wrong for this domain, SAY SO and stop. There is an explicit
  stop-trigger on Phase 1: if it exceeds ~2 days or the source/modifier split doesn't fall out
  naturally from the existing code, revert and report rather than pushing through.

UNATTENDED OPERATION — Jahni may start you and go to sleep. Read this before your first tool call.

YOU CANNOT CHECK YOUR REMAINING BUDGET. No tool reports usage or quota. So do not try, and do not
claim to. Instead assume the harder thing: **this session can end at any moment, without warning,
mid-edit, and nobody will be watching.** Everything below follows from that.

CRASH-SAFE DISCIPLINE (non-negotiable when unattended)
1. `git commit` after every coherent unit — a file, a test, a header. Small and often. You are on
   branch `experimental`; `main` is the known-good fallback, so committing costs nothing and a
   half-finished commit is infinitely better than an uncommitted half-edit. Pushing `experimental`
   is fine and expected (it is tracked as `origin/experimental` since 2026-07-29); **never push
   `main`.**
2. Maintain `OPSTACK-PROGRESS.md` at the plugin root. APPEND (never rewrite) a dated entry per
   milestone: what you did, what you believe is true, what is UNVERIFIED (i.e. everything not yet
   built), and the single next action. Write the entry BEFORE starting the work it describes, so an
   abrupt death still leaves an accurate marker. This file is how the next context resumes.
3. Never leave a file mid-transformation across a stopping point. If you're partway through changing
   a signature and its call sites, finish all call sites or revert the change. A tree that doesn't
   compile *for a reason you documented* is fine; one that doesn't compile for an unknown reason is
   the thing that wastes Jahni's morning.
4. Don't batch a risky change with a safe one in the same commit. If a build fails he needs to know
   which half did it.

WHEN YOU REACH THE BUILD GATE — this will happen quickly, and it is not a failure
The first batch (A + B) is a few hours at most, and then you physically cannot verify anything. At
that point: STOP writing plugin code. Do NOT invent more C++ to fill the night — writing unverified
code on top of unverified code is the exact failure `AUDIT-2026-07.md §P3` documents, and doing it
unattended would be the worst version of it.

Instead work the BUILD-FREE QUEUE, in this order. All of it is genuinely useful and none of it can
break anything:

  Q1. ★ THE DECOMPOSITION MAP — the highest-value unattended task by far. Create
      `OPSTACK-DECOMPOSITION.md`: for EACH of the 8 archetypes, read its density function carefully
      and write out its proposed op breakdown — which FIELD SOURCE, which COMBINERS, which DETAIL
      MODIFIERS, and exactly which existing params migrate to which op asset (field by field, so
      nothing is silently dropped). Note per op: `IsXYPure()`, its `EffectOverBox` strategy, and any
      shared primitive two archetypes could reuse. This is hours of careful reading, has zero build
      risk, and it de-risks and speeds up every later port. Do this before anything else in the queue.
  Q2. Audit the `FStrateGenerationParams` 74 fields against Q1 and list any that no op claims — those
      are either dead or a decomposition gap. Report, don't delete.
  Q3. Tick the stale "PENDING BUILD" markers in `fable-idea.md` / `ARCHITECTURE.md` (confirmed
      resolved 2026-07-26 — everything is built and working; see `AUDIT-2026-07.md §0`).
  Q4. Fix `.gitignore` to `!*.md` — the design docs are currently untracked (`AUDIT P1`).
  Q5. STOP. Write the final `OPSTACK-PROGRESS.md` entry and a clear "good morning" summary: what's
      ready to build, exact likely error spots, what to look at in the editor, what a pass looks like.
      Then idle. Do not start Phase 2. Do not port a second archetype. Do not refactor anything not
      in the plan.

There is no prize for burning the whole night. A small, committed, well-documented, build-ready
increment plus a complete decomposition map is a genuinely good night's work.

Autonomy — what a fresh context can and cannot do alone

Asked: "possibly even, you tell me if it's possible, for you to wait for the token to replenish? to continue it all automatically and on your own." — and then: "can you tell it to check for token remaining before continuing actions? I plan to let it run as I sleep."

On checking the budget: no. There is no tool that reports remaining usage or quota, so an instruction to "check tokens first" would be unfollowable — a safety net that looks real and isn't. The prompt therefore instructs the opposite and stronger discipline: assume the session can die at any moment, unattended, and make every stopping point crash-safe (commit-per-unit, an append-only progress log written before the work it describes, never a half-applied edit). That achieves the actual goal — not waking up to a broken tree — without depending on information the session can't get.

On resuming automatically across a limit reset: the mechanism exists, but don't use it for this, and tokens are not the real limit.

  • A self-pacing loop is available (/loop with no interval, which schedules its own wakeups). Whether it resumes cleanly across a usage-limit reset is not something to promise — treat it as unverified.
  • The actual blocker is the build gate, not the budget. Every meaningful step of a C++ refactor ends at a compile, and rule #1 is that Jahni compiles. A loop left running would therefore do exactly one thing: write more unverified code on top of unverified code — which is the precise failure pattern AUDIT-2026-07.md §P3 was written about (three weeks of stacked "PENDING BUILD"). Automating it would deepen the problem it diagnosed.
  • What genuinely scales instead: batch size. A single context can produce a large, coherent, self-contained batch before stopping. Standing permission for multiple changes per build already exists. So the throughput lever is "fewer, bigger handoffs", not "unattended looping".
  • What IS worth looping: work with no build gate — documentation passes, analysis, or the separate web-based world-rating harness. Not this.

So the honest shape of "handling it on its own": a fresh context can own the design decisions, the code, the doc updates and the sequencing end to end, across many sessions, resuming from OPSTACK-PLAN.md §9. It cannot own verification. That stays with Jahni, and given that the failure modes here are invisible holes and silent seams, that's the correct place for it.

What an overnight run realistically produces

Setting expectations honestly, because the first batch is deliberately small:

~13 h Phase 0.5 tests + Phase 1 skeleton header. Then it hits the build gate and cannot verify anything.
remaining night The BUILD-FREE QUEUE — dominated by Q1, the decomposition map: all 8 archetypes read carefully and broken into source / combiners / modifiers, with every param field traced to its destination op. Genuinely hours of work, zero build risk, and it makes every later port faster.
morning A committed build-ready increment, OPSTACK-PROGRESS.md, OPSTACK-DECOMPOSITION.md, and a "good morning" summary with exact likely error spots.

What it must NOT do overnight: port archetypes, start Phase 2, or write more plugin C++ once the gate is reached. The prompt says this explicitly, twice, because "fill the night with code" is the tempting wrong answer and it reproduces the stacked-unverified-work pattern the audit was written about.


Feasibility, honestly

Phase Effort Risk
0.5 — three tests 1 session + 1-2 build rounds Low. May surface a real bug (that's a win).
1 — skeleton + Maze decomposed 1-2 sessions + 2-3 build rounds Medium — this is the go/no-go.
2 — port remaining archetypes 1 session each, opportunistic Medium; TunnelNetwork last, it owns §8.4.
3 — ops as assets, strate = op list several sessions Medium-high; big authoring-surface change.

The single genuine unknown is Phase 1, and it's cheap to find out — one archetype, timeboxed, with an explicit abort. If Maze doesn't decompose cleanly into a source + a modifier, the abstraction is wrong for this domain and two days bought that knowledge.

The largest hidden cost is not code, it's re-tuning. Every ported archetype needs its params re-dialled to look right again (§2.6 accepts this). That's Jahni's time in the editor, not a context's time writing C++, and it is likely to dominate the schedule.