874c796c65c184f038ca8cafda059860713f077d
32
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
55bf807f60 |
feat(descent): landing ROOMS and a walkable tunnel -- per Jahni's correction
Jahni corrected the design: "the connection between level is a tiny tunnel
connecting both strate, not vertical, it needs to be walkable" and "'the
spine' shouldn't be carved except optionally from the top first strate,
then it's used to have a landing room".
COMPOSER-NOTES §6.8 had said the spine "provides a clean landing space, not
a continuous open shaft" -- and the implementation carved a vertical
cylinder anyway. A vertical cylinder has no walkable floor except at its
bottom, so a carve guaranteeing open AIR guaranteed nothing STANDABLE.
That one omission is behind five of my dead hypotheses this week.
Now:
origin spine a finite rounded ROOM per strate, no vertical bore.
radius 14 -> 8 m wide, 7.5 m tall
descent a walkable tunnel, single-file by design: 3 m mouth,
2.5 m waist, gradient capped at a NAMED 15 deg
exotic styles VerticalShaft / SpiralDescent / CascadingDrops /
CrackCrevice preserved, no longer the default
⚠️ THE GEOMETRY BITES, and it is worth Jahni's eye: at 15 deg a 32 m drop
needs 119.4 m of horizontal run -- about 59.7 m per switchback leg. That is
a long walk between strates. It is the honest consequence of "walkable",
not a tuning artifact, and the alternative is a steeper number.
⭐ AND THE ROOT CAUSE GOT NAMED A THIRD TIME, by Codex mid-run: "the round
tube was hollow air but had no authored support strip, so 'open' still did
not mean 'standable'." Tunnels now carry a floor band, and the root join
terminates in the finite origin room rather than an annular shaft hub.
Measured, commandlet sweep, 8 archetypes x 8 seeds, 64/64 valid:
origin column has player-fit cells 17/64 -> 63/64
origin column reachable 0/64 -> 7/64
arrival landing probes 315/320 fit AND in the arrival component
tunnel audit 5,916/5,916 floor and capsule samples pass; zero gradient,
floor, capsule, geometry or seal failures
narrow-gap events 0 box verdicts 14 checked, 0 violations
⚠️ arrival->departure is 6/64. The landings and tunnels are now provably
walkable; what remains is the INTRA-STRATE vertical connectivity problem
found in
|
||
|
|
413c15de24 |
feat(passages): §6.5 landings -- you arrive somewhere you can stand
The last unbuilt Tier 1 item. A passage was a bore through rock with no floor inside the walkable angle, so its mouth was not a standing position and neither was anything near it -- the departure mouth measured a FOUR-CELL player-fit component. You arrived and there was nowhere to stand. Both ends of all 7 passages now open into a landing, sized from the body: capsule diameter 2.72 vox floor >= 12 vox (3 m) to stand and turn capsule height 7.04 vox room >= 12 vox tall, stock mouth gives 18 (4.5 m) 3-voxel floor slab, tube tangent to it -- no lip to climb out of connector 5-vox radius: 2.5 m clear width, 3 m clear height ramps over 44 deg get a deterministic level dog-leg ⭐ THE MOUTHS ARE NO LONGER SPECKS: archetype components largest arrival-departure Maze 309 -> 340 176 -> 3210 43-81 -> 3210-3210 TunnelNetwork 467 -> 227 1292 -> 3841 2-1 -> 3841-269 Underwater 86 -> 117 35 -> 3838 3-11 -> 3838-267 FlatPlain 45 -> 11 259 -> 2130 1-1 -> 268-273 CrystalChamber 197 -> 90 24 -> 286 1-1 -> 286-274 FloatingIslands 2 -> 1 3 -> 72 1-1 -> 72-72 Law 1/8 -> 2/7 connected (1 refused on MaxCells), and critically ZERO connections now run through a four-cell pocket. Every landing floor was verified against the FINAL density, after all posts: 14/14 pass the 44 deg slope bracket, worst room gradient .059, worst connector .522. 14/14 deterministic root joins. 0 seal failures. 0 box-verdict violations across 82 proved + 174 mixed, 109,142 voxels. Op-stack bit-identical, order-independence green over 10 slots / 31 passages / 3 pool orders. Cache 0.665 us rebuild, 0.646 us hot. Post order: origin spine -> vertical seal -> passage tube + landing floor -> XY edge seal. Landings clamp 4 voxels inside the vertical seal; the XY seal stays last and wins at world edges. ⚠️ ARCHITECTURAL NOTE, not a detail: landings join a deterministic annular ROOT HUB at (0,0), built identically for every endpoint -- no nearest- component search, no passage ordering. That is what makes the join guaranteed rather than likely, and it extends the (0,0) spine concept from a landmark into a structural network. It also means routes tend to pass through the centre, which is a gameplay consequence worth Jahni's eye. ⚠️ AND IT DOES NOT CLOSE THE LAW. Codex is explicit: the landings guarantee membership in the new root network, NOT in the archetype's pre-existing fit component. The whole-strate Maze check still shows a 71.9-voxel gap between 1,235 and 2,938-cell components. So the native corridor networks remain disconnected from the root network in places. Real, separate, next. 52 build-and-measure cycles, ~9 hours, finding real geometry bugs on the way (a landing floor overlapping the ramp cells and hiding a floor discontinuity; the disturbance pass refilling connector headroom). Build clean, 32/32 success, 0 failed. Showcase regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K8V9my1PL68pmQb2gutZZV |
||
|
|
e4e159f0cd |
test(measure): the window was NOT the problem -- my sixth hypothesis dies
I thought the fitted mouth-AABB window was cropping an origin-routed path and producing a false NotConnected. Codex tested it three ways at step 1 on the Maze candidate Jahni walked: mouth-AABB + margin 842,996 cells NotConnected origin-inclusive 3,251,556 cells NotConnected WHOLE STRATE 31,881,528 cells NotConnected 115,578 fit cells, 2,903 components, largest 11,418 -- and the mouths sit in components of 172 and FOUR cells, 274 voxels apart. The window was never the problem. Sixth measurement hypothesis, first one that was wrong in the other direction: this time the instrument was fine and I doubted it. The window rule still improved: bIncludeOriginInCoverWindow now covers the route an origin-rooted topology actually implies (Maze, VerticalShafts, TunnelNetwork, Underwater), expansion happens BEFORE MaxCells, and an over-cap window is REFUSED honestly instead of silently cropping back to a mouth-only box. VerticalShafts refused at the 4M production cap and said so. Corrected law across eight: 0/7 measured Connected, 1 unknown (CoarseLiedBudgetExhausted), 1 refused. ⇒ FIVE CAUSES NOW ELIMINATED: size (Jahni walked it), the fit stencil (fixed, 309 pockets -> 96), placement (mouths land on legal poses), corridor connectivity (spanning tree, 0 disconnected cells over 64 seeds), and the measurement window (whole-strate says the same thing). ⭐ AND THE REMAINING CAUSE IS NAMED BY ITS OWN NUMBER: the departure mouth sits in a FOUR-CELL player-fit component. A passage tube is a bore through rock -- sloped or vertical, no floor within the walkable angle -- so the mouth point is not a standing position and nothing around it is either. You arrive and there is nowhere to stand. That is precisely COMPOSER-NOTES §6.5 / Tier 1's unbuilt item: "the passage mouth becomes a room plus a distinctive landmark". It was never started. The law cannot pass while a mouth opens into a bore instead of a landing. Build clean, 32/32 successful, 0 failed, same two known warnings. WorldRadiusVoxels=0, lateral gating, determinism, equivalence all intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K8V9my1PL68pmQb2gutZZV |
||
|
|
2eaceb5984 |
feat(maze): a spanning tree -- it is a maze now, not a grid
Jahni: "mostly a cube full of straight pathway along the edges... basically
just a repeating pattern of tunnel connecting to each other, definitely
unwanted". And the measurement: the arrival mouth in a 30-cell pocket while
the network held 1,217. SAME CAUSE -- edge selection was independent hash
gating per +X/+Y/+Z edge, i.e. a random subgraph of the cubic lattice.
Every junction identical, and connectivity left to chance.
Fixed the way VerticalShafts was fixed in
|
||
|
|
4378385b37 |
fix(passages): land mouths where a BODY fits -- and the cause is now cornered
VF_SuggestLandingPoint aimed at open AIR. Air is not where a body fits: a mouth could sit in a perfectly open pocket no capsule can stand in. It now requires a legal capsule pose -- clearance (r 1.36, half-height 3.52), support within a 1.8-voxel / 45 cm step height, a walkable support PATCH (4 of 5 columns, centre required, slope <= 44.8 deg), positive density throughout the capsule, strict 2x seal interior, bounded lateral snap. It stays a PURE free function, which was the hard constraint -- GeneratePassages runs inside Initialize while the layout is half-built. So it cannot flood-fill, and Codex correctly did not pretend otherwise: it aims at deterministic guaranteed features instead (room centre/floor, slab floor, horizontal maze edge, shaft feature-core ledge pose, island top). Both mouths query their own strate independently; no passage chaining, so LayoutOrderIndependence stays green. Placement measurably improved: Maze comps 309 -> 82, largest 176 -> 1217, gap 206 -> 122 VerticalShafts comps 112 -> 36, largest 96 -> 619 CrystalChamber gap 47.5 -> 19.1, departure mouth now in a 717-cell comp Underwater comps 86 -> 13 ⚠️ AND THE LAW IS STILL 1/8. Reported honestly rather than tuned. Mouths now land on legal body-sized poses; the poses just are not connected to each other. ⇒ BY ELIMINATION, THE CAUSE IS GENERATION. Four candidates, three dead: size DEAD -- Jahni walked it; bore measures 3.62 m vs 3.677 m spec the stencil FIXED -- fragmentation was 3x overstated by an exact-Z-1 floor placement FIXED -- mouths now sit on legal poses, several in large comps GENERATION STANDS -- body-sized space exists in big components that do not reach each other ⭐ AND THE MAZE TIES TWO OF JAHNI'S COMPLAINTS TOGETHER. Its arrival mouth lands in a 30-cell pocket while the main network holds 1217. Edge selection is INDEPENDENT HASH GATING per +X/+Y/+Z edge (VoxelDensityOpStack.cpp:582/584/586) -- so the corridor graph is a random subgraph with no connectivity guarantee, exactly the percolation failure VerticalShafts had before its drainage tree. That is the same root cause as "mostly a cube full of straight pathway along the edges": a spanning tree would make it BOTH feel like a maze (dead ends, uneven runs) AND be connected by construction. One fix, two complaints. Codex found a real error in my spec: an infinite shaft cylinder has no support surface AT its axis, so the old exact-axis assertion was unsatisfiable. It now requires a deterministic feature core and audits tree connectivity from the resolved axis. Build clean, 29 succeeded / 0 failed / 2 known deliberate warnings. Box audits 0 violations. Shaft refinement 16/16. Order-independence green across 10 slots, 31 passages, 3 pool orders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K8V9my1PL68pmQb2gutZZV |
||
|
|
fc43a86709 |
feat(scale): size the world to a human body -- and it is NOT enough
Jahni: "can't you try and make the world to scale?" Every length parameter is now derived from the body and annotated with its metre value, so nobody has to do the arithmetic to notice something is absurd. CorridorRadius 4 -> 8 1 m -> 2 m radius (4 m bore) CellSize 40 -> 64 keeps a real wall between wider corridors TunnelMinRadius 3 -> 6 0.75 m -> 1.5 m ShaftMinRadius 5 -> 8 1.25 m -> 2 m IslandMinRadius 18 -> 24 4.5 m -> 6 m MouthRadius 6 -> 8 passage mouths 4 m plus passage reach, spiral, cascade, bridges, ridges, chasms The reasoning that motivated it still looks right: a 2 m circular bore for a 1.76 m player clears the head only along the exact centreline, so almost no cell has a floor with 7.04 voxels of clear air above it. A 4 m bore is genuinely walkable. ⚠️ BUT IT DID NOT FIX TRAVERSABILITY, and this time it is not the instrument. The player-fit law is measured at STEP 1 on a mouth-fitted window -- full resolution, both mouths inside the box -- and across the eight showcase archetypes it reports: 6 NotConnectedAtThisResolution (real negatives at full resolution) 2 OutOfWindow (unmeasurable, not passing) 1 StartCellNotPlayerFit (the arrival mouth itself is too small) 0 CONNECTED And every archetype reports hard-gate survivors=0, so the showcase is displaying the least-bad of an entirely failing set. ⛔ DO NOT READ player_fit_fraction / player_clearance FROM THE SHOWCASE LINE. Those come from the COARSE step-4 measurement in the same log line, where the 1.36-voxel player is sub-cell and fit is unmeasurable -- player_clearance reads a uniform "1.00 voxels" for all eight archetypes, which is one cell, not one voxel. Only restricted_law is at step 1 and only it is trustworthy. Fixing that mixed-resolution reporting is next. So scale was necessary and insufficient. My hypothesis was that bore diameter versus body height was the binding constraint; doubling it moved nothing measurable. Something else is preventing player-sized volume from existing at all -- worth finding before tuning any further. Codex applied these edits and then died mid-poll without validating them (third time a killed agent left a clean-looking unbuilt diff). Build and suite run by me: build clean, 30 succeeded / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K8V9my1PL68pmQb2gutZZV |
||
|
|
faa62d419e |
feat(runtime): the composed world can BE the game world
Jahni: "can you not implement those settings? as in, have them be live in
game?" This is that. The composer stops being a lab tool.
THE ARCHITECTURAL LINE, which is the whole design (COMPOSER-NOTES §3.1):
COMPOSITION stays editor-only, EVALUATION becomes runtime. Rolling,
measuring, preview, selection, promotion, the corpus -- none of it reaches
a player's machine. What crosses into the runtime is exactly two things:
reading a season manifest, and materialising a recipe into an op stack.
UVoxelSeasonAsset a cookable UPrimaryDataAsset holding the manifest,
so a season ships in a packaged game. The JSON
stays the reviewable, git-diffable artefact.
UVoxelSettings::Season optional. Unset => behaviour is exactly as today.
GetEffective*() season seed / spine radius / world radius / season
number override the authored values when assigned.
The test deliberately sets DIFFERENT authored
values, so "the season is authoritative" is proved
rather than coincidental.
⭐ MULTIPLAYER DETERMINISM, PROVED: two independent runtime managers built
the same season and compared 393,216 density samples BIT-IDENTICALLY. This
is the property the whole multiplayer model rests on -- replicate seed +
layout + diff, never geometry.
⭐ AND A STALE SEASON IS DETECTABLE, NOT SILENT. Initialize() now returns
bool and an assigned-but-invalid season NEVER falls back to the authored
pool. Falling back would hand a client a world that looks fine and is not
the season -- silent divergence, the worst failure mode in a shared world.
GetCurrentSeasonContentHash() is exposed BlueprintPure to compare at join.
HasComposerRecipeOverride() is gone. It force-classified every custom recipe
as Mixed, which was correct while box verdicts were only proved for
hand-written stacks -- but
|
||
|
|
088b40216d |
feat(composer): showcase of all 8 archetypes -- and "not navigable" answered
Jahni asked for one strate per archetype to look at, and said the previews looked "not navigable nor anything except maybe the mazes". Both delivered. ⭐ PART A -- HIS EYE WAS RIGHT, AND SO WAS THE METRIC. BOTH EXPLANATIONS WERE TRUE, and they split by archetype: archetype walkable FLOOR AREA largest connected surface CrystalChamber .059 .9999 1.00 FlatPlain .048 .9985 .9998 SurfaceWorld .064 .9374 .9936 Maze .285 .2629 .9905 Underwater .229 .2061 .3312 TunnelNetwork .155 .2084 .4640 VerticalShafts .108 .1138 .1449 FloatingIslands .004 .1016 .4992 1. THE METRIC WAS WRONG FOR CHAMBERS. Walkable fraction is volumetric: an air cell with solid one step below and headroom above. In a cathedral almost no air cell is near a floor, so a huge open room scores ~.06 while a tight maze scores ~.29. It was measuring CORRIDOR-NESS, not navigability -- and mazes win that by construction, which is exactly the pattern he spotted. Slab families actually have 94-100% walkable FLOOR AREA, fully connected. They were never the problem. 2. AND FOUR ARCHETYPES ARE GENUINELY FRAGMENTED, which is a real content finding, not an artifact. Largest connected walkable surface: VerticalShafts .14, Underwater .33, TunnelNetwork .46, FloatingIslands .50. In VerticalShafts, 86% of the floor is unreachable from the biggest patch. The primordial law still passes -- you CAN get from arrival to departure -- but most of the floor is not part of that. That is precisely "not navigable" and it needed a new metric to see. New metrics: WalkableFloorAreaFraction, NumWalkableSurfaceComponents, LargestWalkableSurfaceShare. A floor you cannot cross is not a floor. Web references checked, per his suggestion -- we invented every metric in this project without once testing them against reality: NPS solution-cave and Mammoth Cave morphology, Lehman Cave dimensions (Talus Room 90x376 ft, 21,511 sq ft floor, 113 ft ceiling), Deep Rock Galactic cave-design notes, Team Cherry on Metroidvania maps. They support heterogeneous room+passage space with branches and shafts -- NOT a universal floor-to-volume target. So there is no single number to optimise, which is itself worth knowing. PART B -- Saved/VoxelForge/Showcase/index.html, 8 cards, 64 PNGs, fine renders (step 1, radius 64, centred on LargestComponentPoint), 0 blanks. Each card carries the exact PIE values to type in, because navigability is judged by walking: CrystalChamber seed 7331 idx 34 slot 1 Maze seed 0 idx 58 slot 1 FlatPlain seed 0 idx 4 slot 1 SurfaceWorld seed 0 idx 11 slot 2 FloatingIslands seed 0 idx 34 slot 1 TunnelNetwork seed 0 idx 61 slot 1 Underwater seed 0 idx 46 slot 1 VerticalShafts seed 0 idx 15 slot 1 All bComposerRollStructure = false. Selection searched seeds 0/7331 x indices 0-63, applied the three hard gates, then scored floor_area + .25*clearance + .10*largest_surface. Caveats kept honest: floor area is a sampled XY proxy, projected connectivity is not a 3D traversal proof, "best" means best within the searched range. Build clean, 30 succeeded / 0 failed, verified by me. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
dd38832bb6 |
feat(composer): lateral REGIONS -- built, measured, and gated OFF
Jahni's own idea: "archetype could even mix within the same strate at some
point, tunnel network leading to a big chamber". An archetype becomes a
spatial REGION inside a strate, not a label on it.
Built by extending the mechanism that already exists rather than inventing
one: the stack blends VERTICALLY between strates, so regions blend the same
way LATERALLY. Deterministic jittered Voronoi sites on a 256-voxel lattice,
1-3 regions per strate from (seed, strateIndex), 24-voxel symmetric blend
band, per-chunk lookup cache. Parameters are never blended -- a shaft's
ShaftSpacing means nothing to a slab -- only the resulting DENSITY is.
Structural posts stay global and unconditional, so an invented region
cannot switch off the primordial law.
single-region path BIT-IDENTICAL to legacy, 256 probes, native + recipe
cross-boundary boxes 20 of 40, 199,800 voxels brute-forced, 0 VIOLATIONS
(every seam-spanning box correctly returned Mixed)
blend-band cost 663 ns vs 338 ns per voxel, over 16.0% of voxels
~1.15x field-weighted
survival 16/16 valid, non-vacuous, largest share >= .50
⛔ AND IT IS GATED OFF, because the seam breaks the one law that matters:
arrival -> departure across a region boundary passes 9/16 seeds, against
11/16 same-region controls in the same harness. Density blending alone does
not guarantee a player can cross a seam. A strate that mixes archetypes and
strands you at the boundary is strictly worse than one that does not mix.
VF_LateralRegionsAreShippable() returns false and the test ASSERTS THE GATE
IS SHUT while the rate is imperfect -- so flipping it without fixing
connectivity turns the suite red by design. The rate is a warning, not an
assertion: it is the measured state of an off-by-default feature, not a
regression in a live world, and a permanently red suite stops being read,
which is how real failures get missed.
Everything that ships is still hard-guarded: single-region bit-identity,
zero cross-boundary box violations, and every shipping-path law test
untouched.
The fix is an explicit CROSS-SEAM CORRIDOR CONTRACT -- a design decision,
recorded rather than made unilaterally. Codex diagnosed this correctly and
declined to tune the threshold, which was the right call.
Build clean, 29 succeeded / 0 failed, run and verified by me.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
|
||
|
|
42e1f0ec8c |
feat(composer): Tier 4c -- a season that can actually ship, with a veto page
The composer could invent, measure and show strates. What did not exist was an ARTEFACT a season could ship. Now it does. VF_ComposeSeason: generate N candidates -> measure -> reject failures -> select a descent sequence -> emit a JSON manifest + a review page. ⭐ THE PROPERTY THAT MATTERS: round-trip bit-identity. The manifest was loaded, every selected stack rebuilt, and 393,216 density samples compared BIT-IDENTICALLY. Re-composing the same seed yields byte-identical JSON. A manifest that cannot reproduce its own world is worthless, and that is the one thing that could not be assumed. Floats serialise as readable values PLUS exact IEEE bits, so a season survives a text round trip. Test season: 24 generated, 19 survived the hard gates, 6 selected. 18 not selected -- 4 vacuous, 1 primordial-law failure, 13 policy non-selections. 4 grounded, 2 outliers. EVERY selected slot passes the primordial law, asserted per slot rather than sampled. Selection policy is explicit, named and PROVISIONAL, because Jahni has not defined "good" yet and said so: "it'll be easier once i'm able to see the strates candidates." So no quality score was invented and presented as settled. The knobs are 75/25 grounded-to-outlier (outlier axis = distance from corpus centroid), adjacent-similarity threshold .20 with penalty 1.0, new-archetype bonus .10, new-recipe bonus .05, boss cadence every 5th non-terminal slot. All of them are his to turn. The veto is a review page in DESCENT ORDER -- the season read top to bottom the way it is played, not the survivor/outlier ordering the candidate sheet uses. Reuses VoxelStratePreview rather than a second renderer. Saved/VoxelForge/SeasonTest/season_0000_seed_9320/season_manifest.json .../review/index.html ⚠️ FIVE GAPS CODEX RAISED, recorded not buried: 1. §4's Tier A is strates PLUS passages; only the strate spine is persisted. Manager-generated passage geometry and content placement are not. 2. Authored fixed strates keep scalar vectors + asset paths, not every asset-level passage/biome/disturbance/content field. 3. Boss slots are MARKERS, not arena or content guarantees. 4. WorldRadiusVoxels == 0 is enforced as specced but contradicts a production bounded world -- Jahni's call, still unset by design. 5. Bit identity is proven for the same build and platform. A frozen season spanning engine versions needs an explicit schema contract. Real risk, since seasons freeze generation for months. Build clean, 28 succeeded / 0 failed, verified by me. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
0b3ec18b43 |
feat(composer): PROMOTION -- the corpus grows itself, and it did not collapse
Tier 5. §3.3's answer to Jahni's "I'd like the system to not rely at all on
the handmade ones": promotion does not REMOVE the authored seed, it dilutes
it. Every invented strate that measures well rejoins the corpus.
Five simulated seasons, 24 candidates each:
season corpus project/default/promoted survival spread
1 15 4/8/3 20.8% 0.869
2 20 4/8/8 54.2% 0.849
3 20 4/8/8 54.2% 0.849
4 21 4/8/9 45.8% 0.916
5 21 4/8/9 50.0% 0.916
Corpus 12 -> 21, of which 9 are discovered. Survival roughly doubles.
⭐ AND THE COLLAPSE RISK DID NOT MATERIALISE, which was the thing worth
measuring rather than assuming. Promotion selects survivors, and blending
survivors repeatedly can converge the corpus onto a narrow safe region --
the composer would get better at passing and worse at surprising. Spread
went 0.869 -> 0.916. It dipped and plateaued mid-run, but did not narrow.
Recorded honestly: seasons 3 and 5 promoted ZERO. The near-duplicate gate
(normalised metric distance < 0.20 over air, largest share, walkable,
feature scale, clearance) is doing its job, but it also means the corpus
saturates at this scale. Five seasons of 24 demonstrates the MECHANISM, not
§3.3's "season 5 blends 200 discovered ones" -- that needs the overnight
budget, not a test.
Safety: a promoted record is RE-MEASURED on load, never trusted. Verified by
corrupting a stored metric -- the loader discarded it and re-derived the
geometry. A stale or tampered record therefore cannot introduce an
unescapable strate into a future season, which is the one failure that would
matter. Provenance (project/default/promoted) is tracked and logged.
Codex's recommendation, and I agree: keep a small explicit OUTLIER QUOTA
among candidates that pass every hard gate. This run showed no collapse, but
survivor-only promotion keeps the long-term risk, and a quota costs nothing.
PART A: the fine ROI now centres on LargestComponentPoint instead of (0,0).
Blank fine renders 6/42 -> 3/42. The label now reads "largest open space",
not "strate centre" -- the image moved, so the caption had to.
Build clean, 27 succeeded / 0 failed, verified by me. WorldRadiusVoxels=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
|
||
|
|
43eaf1ef0e |
test(composer): the handmade corpus is load-bearing for QUALITY, not validity
Answers Jahni's standing wish directly: "in the best of both worlds, i'd like the system to not rely at all on the handmade ones." Three arms, same size, same measurement, same criteria: approach survival walkable mean feature-scale mean corpus blend (today) 13/16 81% 0.0661 92.3 naive uniform 7/16 44% 0.0126 64.6 constraint-sampled 7/16 44% 0.0129 31.4 ⭐ THE ANSWER IS NOT THE SURVIVAL RATE, IT IS THE QUALITY GAP. Constraint sampling produces VALID worlds -- it passes the primordial law more often than naive rolling (1 law failure vs 3) -- but the survivors are materially worse: ~5x lower walkable fraction and ~3x lower feature scale. A composer that emits technically-passing but uniformly mediocre places has not solved the problem. So the corpus carries something the declared constraints do not. §3.3 said independent rolling destroys the CORRELATIONS; stating those correlations explicitly recovers validity but not the tacit quality that authored vectors encode. That is worth knowing and it was not knowable by argument. ⇒ VERDICT: keep the corpus in production. Constraint sampling is a corpus-free BOOTSTRAP (it can seed a corpus from nothing), and PROMOTION (Tier 5) is the real route off handmade content -- dilution, not removal. Naive rolling is rejected outright. ⚠️ UNDER-POWERED, stated plainly: 16 candidates per arm, not the 256 the spec asked for -- the fixed 8M-cell box/law workload made 256x3 too slow to finish. The 5x walkable gap is large enough to be believable at n=16, but the survival rates (44% vs 44%) are not separable at that sample size. Do not quote these as precise. Authored-corpus audit, and Codex read it correctly: 3 of 12 vectors violate the proposed relations (DA_Strate3 room coverage 2.68 vs bound 0.35; Default_SurfaceWorld ground bound; Default_VerticalShafts shaft radius 5 vs roughness margin 5.875). That does NOT mean those assets are broken -- it means the new relations are conservative HYPOTHESES, not universal corpus invariants. Recorded as such. Box law held across all three arms: 768 proved, 673,486 voxels brute-forced, 0 violations. §6.2 did not reopen under corpus-free parameters. Also noted: "uniform within clamps" was underspecified in my spec -- many fields have no finite upper clamp. Both corpus-free arms use the same declared H-relative envelopes so the comparison stays honest. Build clean, CorpusFree green, WorldRadiusVoxels=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
d717bc74ff |
feat(editor): walk a composed candidate in PIE + why rooms read angular
PART B -- the thing Jahni asked for: "any way to test those in game?" A candidate existed only as measurements and PNGs. Now he can stand in one. Select the VoxelWorld actor in PIE -> Live Edit|Composer -> ComposerSeed / ComposerCandidateIndex / ComposerTargetStrateIndex / bComposerRollStructure -> click Apply Composer Candidate. The button and the automation tests call the SAME roll wrapper, which was the load-bearing requirement: a second roll path could drift, and then he would be judging a different world than the contact sheet describes. A determinism sample is logged on apply (seed, index, recipe, archetype, metrics, epoch, eight-point density verdict) so drift is caught the day it happens rather than in a month. Hazards handled, since this is the first composer code to touch the live runtime path: preflight materialisation before any live mutation, generation and decoration tasks drained, PassagesVersion bumped to invalidate thread_local caches, reuse of the existing RegenerateAllChunks / Epoch invalidation rather than a new path, no Initialize() or GeneratePassages() during apply (the re-entrancy trap), DiffLayer locks untouched, and custom recipes conservatively force ClassifyTile to Mixed. Scope is deliberate: a DENSITY-ONLY overlay. Layout height, authored passages, content, water and biome metadata stay from the existing slot; RebuildStrates() clears it. A full composed layout replacement needs a safe rebuild path that does not exist yet. Editor-only, verified: a Shipping build succeeds with the bridge excluded (96/96 actions), so §3.1's "offline only, never on a player's machine" still holds. ⚠️ UNVERIFIED BY DESIGN: a button cannot be tested headlessly and Codex was told not to claim it works. Jahni presses it and confirms the visual plus the "determinism=PASS" log line. PART A -- why rooms read angular, measured not guessed. One of three room shapes is VoxelSDF::RoundedBox: ShapeA = (.8 Rxy, .8 Rxy, .8 Rz) ShapeR = .25 Rxy so rounding is 31% of the horizontal half-extent but 78% of the vertical. Corners are rounded; the broad planar FACES survive. SmoothMin (blend 4) softens room/tunnel junctions not isolated corners; the cave warp bends geometry but preserves flat regions; roughness is surface-scale only. The name is accurate -- "rounded box" is not "blob". NOT ACTED ON. Jahni corrected his own read: the angular archetype is Maze (lattice source, rectilinear BY DESIGN and correct), and TunnelNetwork is "alright-ish". Codex's recommendation is recorded for if he ever wants it: ~5-10% boxes, 10-20% capsules, rest ellipsoids, or a larger ShapeR. Also closed REVIEW_FINDINGS EVoxelPassageType/Style as WONTFIX, enacting a 2026-08-16 judgement that recommended closure and was left open. Build clean (Editor + Shipping), 25 succeeded / 0 failed, run by me. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
90c40c8d5a |
feat(preview): fine ROI renders + density contour -- the caves look like caves
Implements Codex's own recommendation from its critique, after it settled
the owner's two observations with evidence:
"hard walls" -> THE RENDERER. rg (room graph) re-rendered at step 1 is
rounded and organic. The hard edges were 4-voxel
sampling drawn nearest-neighbour. Fifth time in this
project that an alarming result was the instrument.
"like a maze" -> REAL AND CORRECT. lt (lattice) stays rectilinear at
step 1 because it IS a maze; 19 of 64 candidates use
that source.
"noisy" -> ALIASING. nr#22 speckle 7 solid / 1 air at step 4 ->
0 / 0 at step 1, broad shapes intact.
Built: an optional fine ROI pass at step 1, radius 64 (~1.8M cells, cap
2M), run only for the 42 coarse survivors, plus a deterministic density=0
CONTOUR rendered beside the filled view.
Both views are kept side by side on purpose. The filled view is honest
about what was sampled and carries the walkability evidence; the contour
is legible. Codex's line, and it is the right principle: anti-aliasing the
coarse image "would only cosmetically blur the evidence and could invent
geometry". Do not replace evidence with beauty.
Fine labels carry their own sample step and resolved window so a fine
image can never be mistaken for a coarse one, and a MaxCells refusal shows
in the page rather than looking like a failed render.
fine sampling 71.1 s · rendering 0.6 s · peak grid 9.34 MB
42 filled pairs, 42 contour pairs, 0 refusals, 424 image links, 0 missing
Codex corrected my spec twice in the critique, both sharp: a binary step-1
raster ALWAYS has one-voxel axis-aligned edges, so only persistent
LARGE-SCALE right angles prove boxy generation (my criterion would have
produced a false "it is boxy" verdict); and persistent speckle is not
necessarily a roughness modifier.
Roughness deliberately untouched -- its call to judge that only after fine
rendering exists, and it stands.
⚠️ KNOWN, next: the fine ROI is centred on (0,0), so a candidate whose
geometry sits elsewhere renders blank (candidate 003 is a flat sheet with
no surface in the ROI). FVoxelStrateMetrics already carries
LargestComponentPoint -- centre the ROI there instead.
Build clean, 25 succeeded / 0 failed, verified by me.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
|
||
|
|
993512f0bc |
feat(composer): F1 -- the candidates are VISIBLE, and detail is invisible
PART B: the preview. Jahni, asked what "good" means for ranking a season: "good is hard to describe, i believe it'll be easier once i'm able to see the strates candidates." So the ranking policy was blocked on this, not on more metrics. COMPOSER-NOTES §3.4 promised the preview comes nearly free once the measurement exists; collected. Per candidate, rendered from the grid the measurement ALREADY built (no second sampling of the world): a vertical XZ section and a plan XY slice, solid dark, air light, walkable cells highlighted, with a chunk scale bar so a corridor is distinguishable from a cathedral. Plus a self-contained index.html contact sheet of all 64 -- survivors first, outliers first within them, recipes, metrics, and every rejected candidate labelled with WHY (vacuous 3, fragmented 2, law failed 19). Saved/ComposerPreview/<runid>/index.html 64 candidates, 128 PNGs Two honesty rules are on the page itself, not buried in a comment: every number states the window that produced it (margin/step/Z span), and a 2D slice CANNOT show connectivity -- two regions that look joined in one slice may not be. Plan-Z is chosen by highest solid/air boundary-transition count, not hardcoded mid-height; a slab strate would otherwise render as a blank sheet. Codex flagged the ambiguity in my spec and picked correctly. PART A: bootstrap jitter for zero-spread fields. ±25% of a field's own magnitude when corpus spread is ~0, ±15% of measured spread otherwise, clamps last. Marked in code as a BOOTSTRAP for a starved corpus that promotion (Tier 5) should retire. 1,422 applications over 168 fields in the parameter roll; 6,848 over 107 in the structure roll. ⚠️ AND IT DID NOT BROADEN THE DISTRIBUTIONS. Feature scale 0-376 either way; walkable actually NARROWED (.389 -> .287). Codex's conclusion, and I agree: the measurement is INSENSITIVE to these fields, not merely corpus-starved. The reason is now obvious and worth writing down: the grid samples every 4 voxels, and cave DETAIL -- terraces, scallops, ribbing -- lives at 1-3 voxels. It is below the sampling resolution. A step-4 grid structurally cannot see feel; it sees size. So "vary the detail" and "score the detail" need different instruments, and the preview images inherit the same limit. Box proofs stayed sound under wider jitter: parameter roll 1,204 proved / 1,602,524 voxels / 0 violations; structure roll 659 proved / 877,129 / 0 violations. §6.2 did not reopen. fable-idea.md F1 ticked (measurement + preview). Build clean, 25 succeeded / 0 failed, verified by me. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
f088c19f88 |
feat(composer): cave FEEL is rollable -- 23 of 34 fields were live all along
Jahni: "we need to be able to have everything tuned, so feeling can be
quite different." So the 34 excluded terrain-detail fields had to be
settled -- they are the difference between varying how BIG a cave is and
varying how it FEELS.
Settled by EXPERIMENT, not by reading, because the exclusion rested on the
same kind of structural claim that produced the withdrawn fuse assertion
and the "99.95% void" reading. Sample GetDensityAt over a lattice, change
ONLY those fields, re-sample, per operation group:
LIVE (23 fields) terrace, layer-line, ribbing, overhang, cliff,
scallop, arch, dome, pinch -> now rolled
DEAD (11 fields) column, pit, chimney -> pre-baked into the chunk cache
from Definition->TerrainOperations, so the values in
FStrateGenerationParams are transport, not tunables
The call chain for the dead ones: BuildParamsFromDefinition returns only
the base params (VoxelStrateManager.cpp:1399); VoxelGenerator separately
reads Definition->TerrainOperations (VoxelGenerator.cpp:1099);
BuildChunkCache calls RoomOp->ApplyTo and pre-bakes pits/chimneys/columns
(VoxelCaveMorphology.cpp:1741).
⚠️ AND THE TRAP FIRED, exactly as specced against: these are native
unreflected fields, so REMOVING THEM FROM THE EXCLUSION LIST WOULD HAVE
ROLLED NOTHING. A knob that looks like it works and does not is worse than
an honest exclusion. An explicit jitter path was needed. This is why the
task was specced as an experiment with a liveness assertion (24 group/path
rows, 0 mismatches) rather than as a list edit.
⚠️ HONEST NEGATIVE: rolling detail did NOT materially broaden the measured
distributions. Feature scale 4-376 -> 0-376; walkable unchanged. All 17/17
TunnelNetwork/Underwater candidates activated live detail and every one of
the nine live groups appeared, so it is not that the fields are inert --
it is that most of them have ZERO CORPUS SPREAD, because the four authored
assets leave them at header defaults. Jitter is a fraction of measured
spread, and a fraction of zero is zero.
⇒ Real limitation, recorded not patched: "ranges come from corpus spread"
(§3.3) is right in principle and DEGENERATES on a homogeneous corpus. The
composer needs a bootstrap for zero-spread fields. Next.
Box verdicts with detail rolled: 1,228 proved, 1,634,468 voxels
brute-forced, 0 violations. Survival 59/64.
Recommendation recorded for later: roll terrain-operation parameter
STRUCTS directly into a transient candidate pool rather than corpus-ing
over assets -- there is exactly one such asset in the project, so an asset
corpus would starve like the strate corpus did.
Build clean, 25 succeeded / 0 failed, verified by me.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
|
||
|
|
859a9b25c1 |
feat(composer): the structure roll -- 64 distinct stacks nobody wrote
Tier 4b. Until now the composer varied PARAMETERS within an archetype:
differently-sized caves. This rolls STRUCTURE, which is what makes a strate
a different KIND of place.
Per COMPOSER-NOTES §3.2: roll root polarity (rock+carve or void+fill) ->
a shape source legal for that polarity -> the conversion FOLLOWS from
polarity rather than being rolled -> k modifiers drawn from what the
CHANNEL DECLARATIONS say is legal, not from a hand-written list ->
structural posts appended automatically and not omittable.
64 distinct recipes of 64 (perfect structural diversity)
invalid recipes emitted: 0 (the roller cannot produce an
order the DAG validator rejects)
survival 46/64 (71.9%) non-vacuous 62, largest-share 60,
law pass 48
⭐ AND THEY ARE REAL PLACES NOBODY DESIGNED. Root polarity as a one-bit
identity lever, exactly as §3.2 predicted:
VF|r=vd/T|s=rg/T|x=fl/T TunnelNetwork's ROOM GRAPH rooted in void with
a FILL -- rooms become solid blobs hanging in
open space. air .978, feature scale 64.
VF|r=vd/M|s=lt/M|x=fl/M Maze's lattice corridors filled instead of
carved. air .938, feature scale 16.
RC|r=rk/V|s=sh/V|x=cv/V shafts carved from rock with a Maze modifier
set they never ship with.
Same parts, different worlds. That is the whole thesis of the op stack,
finally exercised.
⭐⭐ AND THE VERDICTS HOLD ON STACKS NOBODY VERIFIED:
489 proved (213 AllSolid, 276 AllAir), 2,071 mixed
650,859 voxels brute-forced
0 VIOLATIONS
This is what
|
||
|
|
190ff381c0 |
feat(composer): it invents strates that work -- 59/64, and §6.2 is CLOSED
The corpus was starved: the loader followed the active settings pool and
found ONE vector, so all 64 candidates were bit-identical and every number
was a placeholder. It now loads every UVoxelStrateDefinition via the Asset
Registry (Saved/Autosaves and Saved/Cooked excluded -- including them would
silently weight the corpus toward whatever was autosaved last) plus the
per-archetype defaults from VoxelStrateTypes.h, which are themselves
hand-tuned known-good vectors. Blending stays WITHIN an archetype;
different archetypes read different subsets of the 73 fields.
⭐ THE COMPOSER WORKS. 64 candidates blended from real parents, every one
measured:
survival 59/64 (92.2%)
non-vacuous 60 · largest-share >= .50 60 · primordial law 63 · roll
failures 0
and they are genuinely different worlds, not one world 64 times:
air fraction .0137 -> 1.000
walkable .000 -> .389
feature scale 4 -> 376 voxels
archetypes TunnelNetwork, VerticalShafts, FlatPlain, CrystalChamber,
SurfaceWorld, Underwater, FloatingIslands, Maze
⭐⭐ §6.2 IS CLOSED, and it was the real risk. Box-verdict bounds were only
ever proved against the parameters Jahni typed; the whole worry was that
invented parameters would violate a bound nobody had checked -- silently,
leaving no geometry and no collision. Against ROLLED parameters:
1,224 boxes proved (549 AllSolid, 675 AllAir), 1,336 Mixed
1,629,144 voxels brute-forced
0 VIOLATIONS
The bounds hold across the rolled range, not just at authored points. The
isolation work in
|
||
|
|
358bd415a9 |
feat(composer): the parameter roll -- infrastructure real, corpus starved
Tier 4a. The first piece of the composer that INVENTS rather than fixes. The design, from COMPOSER-NOTES §3.3: do NOT roll 73 parameters independently. The coherent region of a 73-dimensional space is a vanishingly thin sheet and uniform sampling never lands on it -- and independent rolling destroys the CORRELATIONS (room spacing <-> room radius <-> tunnel length) that make a vector coherent. So blend known-good vectors through the existing FStrateGenerationParams::Lerp instead: constraint satisfaction is free, because a convex combination preserves every relation holding in both parents. Min <= Max stays true because it was never broken. Built: FVoxelStrateCorpus (loads authored vectors, computes per-field spread), VF_RollStrateParams (2-3 weighted parents, convex blend, jitter by +-15% of each field's MEASURED corpus range -- not by the clamps, which are "don't type nonsense" limits, not "this makes a good world" limits), and VoxelForge.Composer.ParameterRoll. Determinism asserted by rolling twice and comparing every field. Exclusion list found by reflection, not assumption: StrateTopWorldZ and StrateBottomWorldZ (manager-owned runtime bounds) plus 34 terrain-op transport fields (TerraceStepHeight..PinchLength) that are copied in from room operators rather than being strate tunables. Bools inherit from the dominant parent; ints/enums use Lerp's existing snap. ⚠️ THE RESULT IS VACUOUS AND SAYS SO. 64/64 candidates "survived" -- but the corpus loaded ONE vector (DA_Strate3), so all 64 are bit-identical. 100% survival of a single point is not evidence of anything. The box- verdict check is vacuous too: 2,560 boxes, 0 proved, 0 voxels brute-forced, so §6.2 is NOT closed. The test emits a warning saying exactly this rather than reporting a clean pass, and no workaround was added to hide it. Four strate assets exist (DA_Strate1..4); the loader found one. Fixing the corpus is the next task -- with one parent there is nothing to blend, and every number above is a placeholder. Also owed: clamp metadata is editor-only, so the safety net needs a cook-time baked table before this ships. Nothing calls the composer from any runtime path (§3.1: offline, once per season, on a build box). Build clean, 23 tests / 0 failed (1 carries the deliberate vacuity warning), verified in VoxelM.log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
8e346a3054 |
feat(opstack): EffectOverBox correct in ISOLATION -- the blocker is closed
COMPOSER-NOTES §6.1, titled THE BLOCKER, gated everything after it. A source writing only Sdf did not touch density, so it answered the box query ON BEHALF OF the converter that followed it -- fine when a human wrote and verified the pair, unsafe the moment the composer assembles a combination nobody checked. And the failure is the silent one: a tile wrongly proved uniform means no geometry, no collision, no error, and a player falls through the floor. FVoxelBoxHypotheses now carries FVoxelBoxSdfInterval. SDF writers publish their own [Min, Max]; replacement writers replace it, accumulating writers MinWith it; converters and detail ops consume the prior interval through a state-aware EffectOverBox. Each op answers only for itself and the fold composes them. Unknown, invalid, non-finite or unimplemented bounds force Mixed -- when in doubt the answer is Mixed, because a missed skip costs milliseconds and a false uniform costs a player. Bounds are SUPREMA of what Eval can produce, never the param named "max": FBM 1.5 (not 1.0), max(Min,Max) radius envelopes, warp sqrt(2), SmoothMin slack. [FLT_MAX, FLT_MAX] is the exact no-surface identity. ⭐ THE PROOF IS THE NOVEL STACK. Built a combination no shipping builder produces: ConstantVoid -> LatticeCorridorSource -> SdfRoughness -> SdfFill brute-forced 332,750 voxels across 256 boxes: 250 proved AllAir, 0 VIOLATIONS. If isolation is real an unverified stack must still be sound, and it is. That test -- not the refactor -- is the deliverable. Every archetype re-proved by brute force, 0 violations everywhere: Maze 112/256 · CrystalChamber 38/60 · Underwater 33/40 · FlatPlain 32/60 VerticalShafts 28/60 · FloatingIslands 15/60 · TunnelNetwork prod 9/40 TunnelNetwork dense 0/40 (correctly -- it really is all mixed) COST, reported not hidden: isolation gives up some skipping. TunnelNetwork production 12->9, VerticalShafts 32->28. Answering for yourself alone is strictly weaker than answering for a verified pair. The classifier stays materially useful and the trade buys safe composition, which is the point. Eval is untouched; equivalence tests bit-identical; no per-voxel box query. SurfaceWorld keeps its separate exact height/lattice classifier. Also: ⛔ WITHDREW the "corrective ops must be fused" claim from §3.2. FFloorBiasMod does NOT undo FCaveRoughnessMod -- it adds an independent NormZ^2 x FloorBias term to the ACCUMULATED density and never reads TotalRough. Not a correction, no fuse, no "corrects" dependency. The claim came from reading a name and a comment; one grep would have shown the ten Density-writing ops in between before the paragraph was written. Build clean, 22 succeeded / 0 failed, verified in VoxelM.log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
7b982ad5ae |
feat(opstack): every op declares its channels -- ordering becomes derivable
The composer must assemble stacks nobody wrote by hand. Today the legal order of operators lives in a human's head and in the hand-written builders. COMPOSER-NOTES §3.2: an op's legal position is fixed by which channel it READS and which it WRITES, so dependencies form a DAG and any topological sort of that DAG is a legal stack. EVoxelOpChannel names the two fields FVoxelOpSample actually carries (Density, Sdf) -- read from the struct, not assumed. All 29 concrete density ops declare reads, writes and additive/transformative, filled in by reading each Eval rather than trusting its name. The declarations are PURE VIRTUAL so a new op cannot silently omit them; the composer's safety depends on every op answering. A declaration nobody checks is a comment, so ValidateChannelOrder exists and VoxelForge.OpStack.ChannelDAG runs all eight shipping stacks through it. All pass: TunnelNetwork/Underwater 20 ops, Maze 8, VerticalShafts 9, FloatingIslands 8, FlatPlain/CrystalChamber 6, SurfaceWorld 6. Metadata is snapshotted in FVoxelOpStack::Add, so no declaration virtual is ever called per voxel -- ARCHITECTURE §8.10 respected. ⚠️ THE FUSE WAS DELIBERATELY NOT APPLIED, and the reason is the finding I asked for instead. §3.2 assumed FFloorBiasMod sits next to FCaveRoughnessMod. It does not: TEN ops separate them (Terrace, LayerLine, Ribbing, Overhang, Cliff, Scallop, Arch, RoomColumn, Dome, Pinch), and all ten read and write Density. Moving FloorBias would reorder the live density accumulation, and IEEE-754 addition is not bitwise commutative -- so the note's claim that fusing "leaves the field bit-identical" is false as written. That leaves a real open question for the composer, recorded not resolved: channel rules alone say any Density->Density order is legal, but they cannot express "this op CORRECTS that one". Either the fuse proceeds and accepts a changed field (legitimate -- peer determinism never required bit-identity across builds), or corrective intent needs its own declaration. Settling it needs FFloorBiasMod's actual math read, which is the next question I put to Codex. Build clean, 22 succeeded / 0 failed, verified in VoxelM.log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
271bbdd034 |
docs(composer): record what measuring the world actually found
The notes described Tier 1 as done and had no record of today. Adds §13 (Tier 2 built, and the three artifacts that looked exactly like findings), §14 (the VerticalShafts bug end to end -- three bugs wearing one coat), §15 (working notes that cost something), and corrects the Tier 1/Tier 2 entries in §7. The rule worth keeping, because it held three times out of three: when a measurement reports something alarming, SUSPECT THE MEASUREMENT FIRST. "FlatPlain is 99.95% void", "5 of 15 endpoints disconnected" and "7 of 16 seeds fail" were all method, not world. Also recorded: the two guard directions are not symmetric (a false positive drops a player through the floor; a false negative merely sends you fixing healthy code), percolation is a phase transition and not a knob, and Tier 1 shipped half built in a way only a connectivity measurement could reveal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU |
||
|
|
8cd5face9b |
fix(strate): aim at the room's centre, not beside it
Commit
|
||
|
|
1ab8c0cae4 |
feat(strate): passages aim at open space -- for half the archetypes, measurably
Tier 1. GeneratePassages placed every passage at a random angle, distance and reach, never consulting the destination strate. The carved tube is air by construction, so nothing looked wrong -- but whether the lower mouth joined the destination's connected cave space was luck. A player could descend and arrive in a sealed pocket. The primordial law was a hope. VF_SuggestOpenPointZ is a pure free function of (archetype, params, seed, XY): no operator stack, no StrateManager, no cache. That is deliberate. This runs inside Initialize, where the layout is half-built and LayoutVersion is in flight, so anything holding a manager pointer there is a re-entrancy trap. Coverage is four archetypes of eight, and the number is the deliverable: 7 passages, 3 aimed at generator-confirmed open space with 48/48 ring samples air, 4 falling back. The hole is not closed. It is measured, where this morning it was invisible. Two corrections from Codex, both mine and both would have shipped confident nonsense: room placement is seeded by MakeStrateSeed, not the passage salt, so my version would have queried a cave graph that does not exist; and slabs need the world seed because that is what GetSlabDensity uses. And one of mine caught in review: the first test sampled GetDensityAt at the mouth and asserted air, which is true by construction because the passage carve is a structural-post invariant. It could not fail. Replaced with a 16-point ring outside the carve radius requiring 8/16 air -- a proxy for connectivity, not a proof. The proof is a flood fill, which is Tier 2. Logging: my first spec asked for a per-passage warning on fallback. It fired 572 times and turned 14 clean tests yellow, which is precisely the anti-pattern OPSTACK-HANDOFF.md warns about -- a warning that always means "this is fine" teaches people to ignore warnings. Now one sorted Log line per layout build. Built clean, suite green: 17 succeeded, 0 with warnings, 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPUuCtn5zTLui6WybLbkNb |
||
|
|
10a8bbc677 |
feat(strate): the layout is a set, not a sequence -- and the suite is green
Tier 0. Reordering Settings->StratePool in the editor used to change every world in the game, because Initialize Fisher-Yates shuffled the array in editor order. The pool is now sorted by stable soft-asset path before the shuffle, so the layout is a pure function of (seed, pool contents as a SET). The selection algorithm itself is untouched -- same Fisher-Yates, same cycling, same FixedStrates handling, same Z ranges. Sorting by pointer would have looked right and been load-order dependent, which is the trap here. GeneratePassages walked one sequential FRandomStream across every strate, so changing an early connection count shifted every later passage. Each draw is now a salted VoxelHash of (seed, boundary index, connection index), with the distributions preserved exactly -- ToFloat01 returns [0,1), so Lerp(Min,Max,t) reproduces FRandRange precisely. Codex implemented it and caught a real flaw in my spec: I asked for a test that permutes the pool and asserts the passages match, but once the layout is order-independent that test cannot fail whether or not the passage fix works -- the layout comes out identical, so even the old sequential stream would agree. It added the perturbation that actually bites: change an early connection count and verify every later boundary keeps its own values. It also contradicted me on passage type being randomly drawn. It isn't, and it preserved that. Built VoxelMEditor clean, then ran the suite headless: 16 succeeded, 0 failed, 0 not run. That also closes the thread OPSTACK-HANDOFF.md left open -- |
||
|
|
a60138b19c |
docs(composer): split the transcript -- notes become generator-only
CLAUDE.md says each doc has one job and they must not duplicate. After GDD.md
landed, COMPOSER-NOTES.md and it both held the death loop, the city tiers and
the economy -- and duplicated documents drift until neither is authoritative.
So the notes are now the engineering document and nothing else: the vision and
its correction, what the game *requires* of the generator (constraints only,
pointing at the GDD for why), the composer itself, the manifest, naming, every
code finding and hazard, the build order, and the corrections nobody should
re-derive. 1561 lines down to 527, with the game design replaced by pointers.
Two sections earn their place on their own. Section 6 is the verified code
findings, including the one that blocks free composition (EffectOverBox answers
for the source+carve pair, so an op's box verdict is only correct in the stack
it shipped in) and the primordial law's live hole (GeneratePassages never
consults the destination strate's layout, so a passage can open into a sealed
pocket). Section 8 is the list of corrections that cost real time: Phase 3 is
reopened, Jahni does not want to author strates either, peer determinism never
required matching the old system.
The full 1561-line transcript remains in git at
|
||
|
|
b781e3bab2 |
docs: a game design document, distilled from the transcript
COMPOSER-NOTES.md is a conversation: chronological, with my wrong turns kept on purpose so nobody re-derives them. That is the right shape for us reading it back and the wrong shape for anyone else, including Codex. GDD.md is the other half -- the decided state without the journey. Seventeen sections covering the premise, the core loop, the city, the depths, a strate, descending, the economy, death, housing, the verbs, the season, the community layer and the opening. Every section separates what Jahni decided from what is still open, and design that is mine rather than his says so, so nothing reads as settled that isn't. Written deliberately plainer than the notes: prose rather than dense tables, because a design document that only its authors can read has failed at the one thing it exists for. Also records why the file is in the wrong folder. This is a document about the game, and most of it -- the city, combat, the economy, seasons -- is nothing to do with VoxelForge. It lives in the plugin because the plugin is the only part of the project under version control. It should move to the project root once that becomes a repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPUuCtn5zTLui6WybLbkNb |
||
|
|
b7cf12af4c |
docs(composer): the depths regenerate, the city does not -- game design session
Second design session. Started on the composer, ended up with the whole game's economic loop, and one terminology fix that was overdue. The loop, closed: the surface is a sky island or an unclimbable mountain, so resources must come from below; the depths supply them; the resources build the settlement; the settlement enables deeper descent. The settlement is communal, identical for everyone, and permanent -- a season advances it one tier (camp, village, town, city). So the wipe means something beyond freshness: the world is impermanent, the civilization is not. We lost our maps, but we built a town. I had that arc backwards and it is marked as such in the file, along with the vocabulary that caused it: "the world" was being used for two different things. The depths are what VoxelForge generates and what regenerates each season; the city is a plain Unreal level, permanent, and entirely outside this plugin. That distinction is now pinned at the top of the notes. Also settled: no bottom, boss strates every ~5 as intermediary gates, gear stats capped around depth 30 with deeper loot buying better rolls and unique materials rather than bigger numbers, materials tagged by difficulty tier rather than depth so their location is rediscovered every season, and death leaving a possessed corpse that copies your weapons without taking them -- a mirror match that self-balances at every tier forever, provided the copied gear is never lootable. Two design records worth keeping: the manifest is two things (a tiny stored spine and an infinite hash-addressable field, where the ID encodes the position so resolving it needs no table), and the opening cinematic -- civilization emerged from the depths, humans crystallized in the first artificial strate -- justifies the authored/generated seam in fiction. A player who senses strate 1 feels different from strate 2 is reading the story correctly, not spotting the join. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPUuCtn5zTLui6WybLbkNb |
||
|
|
302cf16681 |
fix(world): the voxel field is in ACTOR space -- make every entry point agree
The city's hole is not at world (0,0), so the AVoxelWorld actor has to move.
It couldn't: the plugin was only HALF actor-relative, and had been for a long
time without symptoms because the actor always sat at the origin.
Already correct: GetBiomeAtWorldLocation, GetVoxelSurfaceHeightAt,
UpdateDecorations, UpdateLandmarks, UpdateWater, UVoxelDensityVolume::Update,
the passage debug draw, the clipmap material.
Not correct: the streaming centre, the streaming anchors, all six carve/fill
entry points, GetStrateAtPosition, and UVoxelAtmosphereManager::UpdateForPlayer
-- whose own comment documented the assumption out loud ("actor at
origin/identity"). Carving would have landed offset; the strate lookup would
have been quietly wrong by the actor's Z.
The trap worth recording: because the content managers already convert
internally, converting PlayerLastPos once at the source would have DOUBLE-
converted four call sites. The fix has to be per-boundary. That was only
visible after reading each consumer -- the estimate before that read was wrong
in both directions.
WorldToLocalCm / WorldToLocalVoxel / LocalVoxelToWorld are now declared as the
only sanctioned boundary between Unreal coordinates and voxel coordinates, and
the real deliverable is the invariant next to them: no `/ VOXEL_SIZE` applied
to a parameter named `World*` outside those three functions. A grep can check
that. The risk was never a missed site among the ones found -- it is the next
one nobody notices.
Also adds a translation-only guard in BeginPlay. Tiles, ClassifyBox verdicts,
the clipmap window, culling and streaming distance all assume world-axis-
aligned boxes; a rotation or non-unit scale breaks that structurally rather
than gracefully, so it logs an Error naming what was set.
With the actor at the origin every conversion is identity, so this is a no-op
until the actor actually moves. Built and confirmed working.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPUuCtn5zTLui6WybLbkNb
|
||
|
|
eae7e68caf |
docs(composer): the composer runs once per season, offline -- so it can take all night
Second half of the design session. The realisation that resets the whole cost budget: everyone shares one seed per season, so there is exactly one world per season and the composer never runs on a player's machine. It runs once, on a build box. Not "fast enough for a loading screen" -- "runs overnight". It can generate a thousand candidates, keep eight, and let Jahni veto the season before it publishes. What ships is the seed plus the chosen parameter vectors. The measurement pass itself is one coarse grid and one flood fill, producing one feature vector that serves validation, danger and rarity at once. Noted the one place coarse sampling can lie -- a wall thinner than the sample spacing makes sealed spaces look connected, which would pass the primordial-law check on an impassable strate. Coarse to find the route, full resolution to confirm it. And the answer to "how do we ever judge what's good": the judge is the corpus, not a model. Good never gets defined, only collected -- strate-level thumbs up/down, player behaviour, and best of all where players choose to snapshot their home, which is an ungameable "I love this place" that costs nothing to collect. Known risk written down: promote only what you already liked and the system converges on a comfortable middle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPUuCtn5zTLui6WybLbkNb |
||
|
|
d8f7dabee8 |
docs(composer): the system should invent worlds -- design session notes
A long design conversation, written down as it happened. The headline: the op stack's "Phase 3" is REOPENED. Asked what the world was missing, Jahni described exactly it -- the system assembling worlds from data assets and a seed -- so the 2026-08-16 handoff's "do NOT start Phase 3" is superseded. Then a correction that moved the whole thing a level deeper: he does not want to author strates either. The system must INVENT them. He authors raw content (operators, meshes, materials, sounds, creatures) and tags it; everything above that is composed and measured. Decided along the way: worlds are wiped every 6-12 months, which dissolves the "generation is frozen forever once public" problem -- generation is frozen only within a season. Shared player builds are blueprint structures, vote-ranked. Housing is an instanced level built from a snapshot of land the player chose. Danger is depth x a per-strate multiplier, not a sort. Every strate has a findable entrance and exit -- built first, generated around. The range problem turned out cheap: don't roll 73 parameters independently (that is what makes mush), blend known-good ones with FStrateGenerationParams::Lerp, which already exists and already blends the whole field set. Ranges come from the spread of the existing corpus, not from authored numbers. Verified against the code: the X-macro is in sync with the struct, only Alpha differs. Recurring mechanism, arrived at three separate times: measure, don't declare -- and promote what measures well back into the corpus, so the system's vocabulary grows from its own history. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPUuCtn5zTLui6WybLbkNb |