From 46f7f5f7c1e15eda88de5f715ce4e19285104594 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 14:33:51 +0000 Subject: [PATCH] docs(fable-idea): tick what the code already does, close F18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Header: drop the false note that .gitignore ignores this file (the whitelist keeps every *.md) and the "nothing here is implemented" line. - Perf §0 / F3 ticked: stat VoxelForge group (VoxelStats.h) + Insights scopes exist. F6 ticked (Stage 1): mesher writes FVoxelMeshData::Colors from MaterialPaletteIndex (ARCHITECTURE §8.15). F7 marked partly done (landmark AnchorMode/exclusion, FTerrainCondition, FDecoCompanion; SDF stamps not built). - F18 far-field sheets: marked REMOVED (owner decision) with what remains (unused settings, runtime passes false). - Suggested-order list annotated with what is done and what is open. - T1.b status no longer repeats the dead-trio removal (tracked in REVIEW_FINDINGS dead code). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01URDGLsu7c4nunqNFPJEFPU --- fable-idea.md | 68 +++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/fable-idea.md b/fable-idea.md index 0ed58fd..e27fa0f 100644 --- a/fable-idea.md +++ b/fable-idea.md @@ -1,14 +1,16 @@ # fable-idea.md — performance & feature ideas -*Fable 5, max-effort pass — 2026-06-09. Grounded in a read of the actual code (mesher, apply path, task launch, content manager), not generic advice. Companion to CODEMAP.md §8 — nothing here is implemented; it's a menu.* - -*Note: the strict-whitelist `.gitignore` will ignore this file. Add `!fable-idea.md` if you want it tracked.* +*Fable 5, max-effort pass — 2026-06-09. Grounded in a read of the actual code (mesher, apply path, task launch, content manager), not generic advice. Companion to CODEMAP.md §8. Items marked ✅ are implemented (evidence inline); everything else is still a menu.* --- ## Part I — Performance -### 0. Measure before anything (half a session, directs everything else) +### 0. Measure before anything (half a session, directs everything else) — ✅ DONE + +*Evidence:* `stat VoxelForge` group (`Public/VoxelStats.h`, `STATGROUP_VoxelForge` tile-classification/meshing +counters) and Insights scopes around the stages (`VoxelForge_GenerateMesh`, `VoxelForge_BuildStreams`, +`VoxelForge_ApplyMeshToChunk`, `VoxelForge_Tick` and its sub-steps). Add `TRACE_CPUPROFILER_EVENT_SCOPE` around the four stages of a chunk (density grid sample / MC loop / normals / RMC stream build+upload) plus a `stat VoxelForge` group: chunks pending, applies this frame, avg gen ms, verts/chunk. One Unreal Insights capture then tells us if we're density-bound (my bet) or upload-bound, and every item below gets a before/after number. Cheap insurance against optimizing the wrong thing. @@ -31,7 +33,7 @@ So: normals can cost as much as the entire density grid; a SurfaceWorld chunk do ### Tier 1 — high win / low risk / small-medium effort (do these first) > **STATUS 2026-07-05: Tier 1 COMPLETE.** T1.a ✅ (surface-column cache, now file-scope `GSurfColCache`); -> T1.b ✅ (grid-based normals — the old per-vertex `ComputeGradientNormal` trio is gone from the mesher); +> T1.b ✅ (grid-based normals inline in `GenerateMesh`); > T1.c ✅ (`bShouldCreateCollision` = level 0 only, VoxelWorld.cpp ApplyMeshToTile); T1.d ✅ (v2 > `ClassifyTile`, trace-verified −44 % worker CPU); T1.e ✅ (`MaxMeshAppliesPerFrame`, default 4); > T1.f ✅ (`BuildTileStreamSet` runs in the gen task; apply only uploads). @@ -106,7 +108,7 @@ the Surface|Macro knobs live, then extend with a top-view passage-path overlay ( **F2 — Determinism validator button.** ✅ DONE 2026-07-04 (`AVoxelWorld::ValidateDeterminism`, Live Edit category). CallInEditor: sample a band of densities from two different chunk-window alignments, diff, report max delta. Turns the scariest invariant (§8.4 — window invariance) into a one-click regression test *before* biome code starts landing. -**F3 — `stat VoxelForge` + Insights scopes.** Same as Perf §0 — listed here because it's also the tool that tells us when a feature regressed something. +**F3 — `stat VoxelForge` + Insights scopes. ✅ DONE** (see Perf §0 for the evidence). Same as Perf §0 — listed here because it's also the tool that tells us when a feature regressed something. ### B. The "this becomes a game" features @@ -116,9 +118,15 @@ Live Edit category). CallInEditor: sample a band of densities from two different `ResolveBiomeSampleAt` / `FBiomeContext`, VoxelGenerator.cpp §biomes; deco borders follow the Voronoi field).** Original sketch (kept for the cave-biome extension): Deterministic XY biome map = warped Voronoi/cellular cells (seeded, window-invariant by construction, same family as the relief map — and relief M should be an *input*: mountain biomes live where M is high). Resolution rules to protect §8.10: resolve **per chunk** (dominant biome + ≤2 neighbors + blend weights, stored in the thread-local chunk cache); per **voxel** blend only a handful of scalars (height offset, roughness, terrace, water tint index). Per biome: a *content profile* — decoration set, atmosphere/audio override, material palette index, water level offset. Archetype transitions stay Hard; biomes vary *within* SurfaceWorld first, cave-biomes (crystal/fungal/ice) reuse the identical pattern later. -**F6 — Material identity: vertex-data masks + triplanar palette material.** Geometry variety without *surface* variety still reads samey. At mesh time, pack per-vertex: slope (from the T1.b normal), relative height, biome/material index (from F5) into vertex color channels. One master material: triplanar rock/grass/sand/snow layers selected & blended by those masks + a macro-variation texture. This is the single biggest *visual* multiplier available and it's mostly material-graph work. +**F6 — Material identity: vertex-data masks + triplanar palette material. ✅ DONE (Stage 1)** — the mesher +writes per-vertex masks into `FVoxelMeshData::Colors` (R = dominant biome palette, G = slope, B = border blend, +A = neighbour palette; `VoxelMarchingCubesMesher.cpp`), fed by `UVoxelBiomeDefinition::MaterialPaletteIndex` +(ARCHITECTURE §8.15). Original note: Geometry variety without *surface* variety still reads samey. At mesh time, pack per-vertex: slope (from the T1.b normal), relative height, biome/material index (from F5) into vertex color channels. One master material: triplanar rock/grass/sand/snow layers selected & blended by those masks + a macro-variation texture. This is the single biggest *visual* multiplier available and it's mostly material-graph work. -**F7 — POI / set-piece system.** Noise terrain everywhere = beautiful nowhere. Deterministic destinations: chunk-hash-placed stamps (composed SDF carves/fills + a decoration prefab + optional ambient actor), e.g. buried shrines, crystal gardens at passage mouths, ruins on mesas. Placement uses the same two-region COLLECT discipline as rooms (§8.4). Destinations are what turn wandering into stories ("found a shrine at −400 m"). +**F7 — POI / set-piece system. ◐ PARTLY DONE** — the placement side is built: `FStrateLandmark` with +`AnchorMode` (HashLattice / PassageMouth), exclusion and set-pieces folded in, `FTerrainCondition` aware +placement, and `FDecoCompanion` satellites (ARCHITECTURE §8.5). Not built: composed SDF carve/fill stamps. +Original note: Noise terrain everywhere = beautiful nowhere. Deterministic destinations: chunk-hash-placed stamps (composed SDF carves/fills + a decoration prefab + optional ambient actor), e.g. buried shrines, crystal gardens at passage mouths, ruins on mesas. Placement uses the same two-region COLLECT discipline as rooms (§8.4). Destinations are what turn wandering into stories ("found a shrine at −400 m"). **F8 — Ore veins / diggable resources.** The game's verb is digging; give digging a reward loop. A secondary material-id field (cheap 3D noise threshold, per-strate/biome tables with depth curves) evaluated **only at mesh vertices** (≈ free) → vertex color → material shows veins; on carve, query the field at the brush center → grant resource. No per-voxel density cost, fully deterministic. @@ -126,27 +134,11 @@ Voronoi field).** Original sketch (kept for the cave-biome extension): Determini **F17 — Generator surface-class tag (ceiling/ground/cave material the *right* way). ✅ DONE 2026-07-05** (per-vertex semantic class in the mesher — down-facing verts query a memoized `GetSurfaceHeightAt`, nearer CeilSurf ⇒ sky-cap — per-tri majority → two contiguous polygroup runs → RMC section per group, slot 1 = `CeilingMaterial`, per-section shadow. Trigger: the whole-tile normal vote painted mixed coarse tiles with one material. Cave-roof discrimination hook is in place: down-facing below TerrainZ ⇒ ground/rock. Remaining polish idea: fully sideways cap-fold tris (all 3 verts |N.Z|≤0.1) default to ground.) Original design note: ★ do this when caves land. Today `ApplyMeshToTile` picks one material per tile from a ceiling test — first a height-oracle sample (midpoint), now a worker-side **normal vote** over the tile's mesh normals (down-facing ⇒ `bIsCeiling` ⇒ `CeilingMaterial` + no shadow; gated to SurfaceWorld by one `GetSurfaceHeightAt` probe). That's a **stopgap that only works because down-facing == sky-cap *while no caves exist*.** The moment a mountain-biome cave uses the same density/mesh system, its roof is also down-facing and would wrongly get the sky-cap material — orientation can't tell a cave ceiling from a surface ceiling. **The discriminator is semantic, not geometric, and only the generator knows it:** a sky-cap surface is the `ComputeSurfaceCeiling` (`CeilSurf`) boundary; a cave ceiling is a 3D-noise **carve** below `TerrainZ`. Cheap test the generator already has the inputs for — at a down-facing surface vertex, compare world Z to the column's `TerrainZ`/`CeilSurf` (both from the surface-column cache the mesher already holds): near `CeilSurf` ⇒ sky-cap, below `TerrainZ` ⇒ cave. **Plan:** stamp a discrete *surface class* (ground / sky-cap / cave-ceiling / cave-wall…) per vertex/triangle **at mesh time** in the mesher → carry it as the **polygroup** (already enabled, `Builder.EnablePolyGroups()`, every tri currently group 0) → `ApplyMeshToTile` maps polygroup → material slot (slot 0 terrain, slot 1 sky-cap, slot 2 cave-rock, biome-specific via the F5 palette mask) and sets per-section shadow. Discrete "which material" → polygroup/slot; continuous masks (biome blend, slope — F6) stay in `Colors`. This **subsumes** the current ground/ceiling split (it falls out as a special case), fixes the coarse mixed-tile horizon artifact exactly (per-triangle, not per-tile dominant-wins), and is the only version that survives caves. Pairs naturally with F5/F6/F8 (all want generator-stamped per-vertex material identity). Cost: a per-tri classify in the mesher (cheap, has the cache) + multi-slot setup in the apply path (RMC supports it; confirm the v5 per-section `UpdateSectionConfig` / slot-per-polygroup calls + empty-polygroup = no draw). Until then: the normal vote is fine — it's commented as "no caves yet → down == cap." -**F18 — Far-field per-surface SHEETS (the render-distance ring, cheap). ✅ BUILT & WORKING 2026-07-06** -(marker ticked 2026-07-27). With `RenderDistanceChunks` the -outermost ring can reach many km — as MC tiles that's 600-1000 primitives paying per-frame visibility/VSM -forever, and each far tile runs full 3D marching cubes just to rediscover two heightfields. In an open -strate the far field IS two heightfields the generator already computes per column (`GetSurfaceHeightAt`: -TerrainZ + CeilSurf). So: the extended ring streams SHEET tiles instead (level `MaxClipLevel + -FarSheetSpanLevels`, so one sheet covers 4-16 MC-tile footprints) and the mesher builds each as two -regular displaced grids — ground sheet (polygroup 0) + sky-cap sheet (polygroup 1), tags true **by -construction** (no vote, no classify probes), same materials/UVs/biome color masks as MC, normals from -the height gradient, perimeter skirts per bucket. Gen ~3-6× cheaper per area than band-cut MC; primitives -~10-16× fewer. Caveats accepted: SurfaceWorld only (non-open strates produce empty sheets — their far -ring was invisible rock anyway; FloatingIslands has no far ring beyond MaxClipLevel), carved features -(passages/spine/chasms) don't show at sheet distance, sheets need the strate band armed (in the -inter-strate gap the far ring blanks until you land). Streaming: sheet ring in `BuildDesiredTiles` -(covered-check vs the MaxClipLevel box), `IsTileInClipRange` shares the same outer shell, -`LoadTile` routes `Level > MaxClipLevel` to `GenerateSheetMesh`. **Build-1 fix (XY hole):** a -partially-covered sheet rendered its *whole* footprint, overlaying the near LOD0-1 terrain with its -coarse sampling. So the MC-covered box around the player (level-MaxClipLevel box, shrunk 1 tile for a -seam-overlap ring) is cut from sheets at cell granularity (`AVoxelWorld::SheetHole*Vox` → `GenerateSheetMesh` -hole args; hole moves on a MaxClipLevel-tile crossing → overlapping sheets re-queue via `BandRemeshQueue`; -hole-edge cells emit no skirt). +**F18 — Far-field per-surface SHEETS. ⛔ REMOVED (owner decision, 2026-09 cleanup).** Built 2026-07-06 as a +render-distance ring of heightfield "sheet" tiles past `MaxClipLevel` (meshed by `GenerateSheetMesh`); the +runtime path is deleted. `UVoxelSettings::bFarSheetRing` / `FarSheetSpanLevels` remain only so saved assets +load, and the runtime passes `bFarSheetRing = false`; the render-distance ring is plain level-`MaxClipLevel` +MC tiles (ARCHITECTURE §8.10). **F19 — AI navigation & agents (function-based). PARKED (no NPCs yet); FOUNDATION BUILT 2026-07-07.** Mobs need two things the world didn't give them: (1) to *exist* away from the local player, (2) to *route*. @@ -228,7 +220,7 @@ selection AND seamless border blending come for FREE with zero new resolution pa ops (fine — biome-differentiated terrain already implies that), and a strate can also carry ops with no biomes at all (more flexible than biome-only). All fields default OFF ⇒ current world byte-identical. Applied in the single height oracle `ComputeSurfaceTerrainZ` (new `SampleSurfaceStructuralZ` helper = pre-op raw height, re-sampled at -an XY offset for Cliff's slope) so MC/sheets/ClassifyTile/deco/BP-bridge all agree, no T1.d interference. Revisit +an XY offset for Cliff's slope) so MC/ClassifyTile/deco/BP-bridge all agree, no T1.d interference. Revisit the array+condition model for **phase 2 (overhangs)** where per-entry slope-gating earns its keep. **PHASE 2 — ✅ BUILT & WORKING** (built 2026-07-08; marker ticked 2026-07-27 — confirmed working by Jahni 2026-07-26, see `Docs/archive/AUDIT-2026-07.md §0`). Overhang (first VOLUMETRIC op) as @@ -267,11 +259,11 @@ Multiplayer — **NO LONGER just "deferred": it's the confirmed direction (liste ## Suggested order (if it were mine to pick) -1. **Perf 0 + T1.c + T1.a + T1.b** — one focused session: measurement, the one-line collision win, the two big density cuts. -2. **T1.e + T1.f + F16** — smooth the game thread (apply budget, worker-side streams, HISM props). -3. **F1 preview tool + F2 validator** — before biome work starts, build the instruments. -4. **F5 biomes + F6 materials** — the look of the game. (F9 audio rides along cheaply.) -5. **F4 save/load** — the moment it feels like a game, players will want to keep one. -6. **F7 POIs + F8 ores** — destinations and rewards. -7. **T2.a SIMD noise** — after content direction settles (it changes seeds), before world-size ambitions grow. -8. **Transvoxel** (already chosen) whenever LOD cracks become the loudest remaining flaw. +1. ✅ **Perf 0 + T1.c + T1.a + T1.b** — one focused session: measurement, the one-line collision win, the two big density cuts. +2. ✅ **T1.e + T1.f + F16** — smooth the game thread (apply budget, worker-side streams, HISM props). +3. ✅ **F1 preview tool + F2 validator** — before biome work starts, build the instruments. +4. ✅ **F5 biomes + F6 materials** — the look of the game. (F9 audio: not built.) +5. **F4 save/load** — the moment it feels like a game, players will want to keep one. *(open)* +6. **F7 POIs + F8 ores** — destinations and rewards. *(F7 placement built, stamps open; F8 open)* +7. ✅ **T2.a SIMD noise** — after content direction settles (it changes seeds), before world-size ambitions grow. +8. **Transvoxel** (already chosen) whenever LOD cracks become the loudest remaining flaw. *(open — skirts fill LOD cracks today)*