58e61cdefa3c3e2da724cc8394e2f39e227d5fe6
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |