Repository navigation
Conversation
Replace the tile quadtree with an octree of 64^3 chunk nodes (face, level, cx, cy, cz). Classification is descent-aware: a coarse node answers for what its descendants hold (DESCENT margin, cave band from volume_bounds, brushes at any size, baked-brick index), so rounding, caves and small digs never hide detail a finer level has. Residency is a cut of the octree. A footprint's contiguous Mixed band switches levels as one; rays report the node their pixel wants (one pixel per 8x8 block per frame) and only keep nodes from refining; Buried nodes draw as rock until rays or the eye reach them; LRU eviction runs under slot and leaf pressure. Settled waits for a full 64-frame sweep of ray reports since the last change. Footprint columns and halos stay on the GPU in a 2048-slot pool across frames (terrain-only, so edits never invalidate them): 19-22 us a chunk against 27-30 before. The old tile, layer, gap and deep-tile paths are deleted. Design, results and invariants are in docs/chunk-octree.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The intermittent capture outliers (8x8 and 4x8 blocks of haze) are not an engine race. The publish writes every pixel, yet those blocks of every G-buffer target and of depth keep the frame's earlier clear. Only NVIDIA's Vulkan driver (616.64, RTX 3060) shows it: D3D12 never does, and synchronization validation finds no hazard. Record what does and doesn't change it, and the report to NVIDIA (bug 6939141). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cells whose material edits set (add, paint, baked cells) keep their own material: chunk jobs with material edits write each cell's edited material into a cell_materials slot, and chunk_build gives every leaf page holding such a leaf a 4 KiB detail page (a byte a cell; 0: the leaf's pair) off a free stack, returned on eviction. Drawing reads the cell's byte first, at the hit and in the top face's material sample; a per-slot flag skips the lookup in chunks without detail pages. PickHit reports the drawn material. queue_footprint finds a footprint's brushes through a BrushIndex (a loose grid per face) instead of scanning every brush per chunk, and decides which chunks fit the batch (detail slots included) before taking a footprint pool slot. Tests: edited cells draw their own materials (16306 pixels exact; 14610 wrong without the lookup); the index finds what a scan finds; a report of natural cells against Planet::material (40.5 % of mountain pixels differ, 38.8 % from the kernel evaluating ground_material at the column's domain point instead of the cell's volume point, 10.7 % from the leaf rule). mountain_slope primary rays and generation cost unchanged within noise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generation kernel evaluated ground_material at the column's domain point and with an in-tile slope; Planet::material (picks, a dig's yield) at the cell's volume point and with level-4 relief. The noise behind the snowline and rock patches is unrelated 2 km up, so whole mountain views drew snow as stone: 40.5 % of natural level-0 pixels at varied spots drew another material than the canonical one. Each evaluation now uses its cell's volume point and the surface byte, and levels 0 to 3 use Planet::material_slope: per computed footprint, a lattice of level-4 heights (a ring of shared nodes, the heights spread over workgroups, then the block slopes) kept in the footprint pool. uniform_leaves_draw_canonical_materials gates it: the top faces of leaves whose columns all top in them with one material draw the canonical material exactly (31710 pixels, 779 wrong before). The natural-cells report now isolates the leaf rule: 9.35 % of pixels, 33 % in dug walls. Generation +2 us a chunk (levels 1 to 3's lattices); drawing unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each GPU test built a game's full-size ChunkWorld (131072 chunk slots, about 2.9 GB of device memory with its device). Eight parallel tests on a 12 GB GPU paged device memory, and the suite crawled or stalled past 25 minutes. ChunkWorld::with_capacity takes a Capacity (chunk slots, detail pages, footprint pool slots); new keeps the game default and its environment variables. Tests use 16384 chunks (the largest view settles with 12901): 1.1 GB a test, the whole suite in 240 s at default threads, peak 5.2 GB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A leaf's subsurface byte follows its centre's depth, so a third of a pit's wall pixels drew rock where the cells are soil a cell deep. Every chunk under edits now gets a cell_materials slot (128 a batch): the columns kernel writes each cell's edit material and the column's natural top (scanned over the chunk as terrain::generated_top does), and chunk_build gives each exposed solid cell without an edit material its natural Planet::material, clears the cells its leaf's pair draws as they are (never in a leaf drawn with its mid's material), and gives leaves with cells left detail pages. dug_walls_draw_canonical_materials gates it: pits at three varied spots, all 57050 wall and floor pixels exact (about 170 detail pages a pit). The natural-cells report: 3.56 % of pixels wrong (9.35 %), none under ground. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's voxel changes were made on the tile-quadtree chunks/mod.rs this branch replaced (with a rustfmt pass); they are ported onto the octree files in their style: the renderer's debug mode forwarded to the voxel shaders (set_debug_mode; local diagnostics in bits 0x200-0x800), the trace-cost view (mode 32, cost in the visibility word's high bits, the material in five), the UV reference grid (mode 1). The grid's texture and sampler take group 1 bindings 25 and 26: 24 is the ray feedback buffer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dug walls' detail pages at every level filled a test world's 2048 after ten digs 8 m across; levels 0 and 1 (DETAIL_LEVELS) take 368. Coarser leaves keep their pairs. edit_cost_against_history (ignored, a report) measures one more edit after n edits: small digs (baked) cost the same at 10000; large ones (analytic) cost 3.2 ms of sync and 13 ms a frame at 10000. The docs plan edits on data from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At 10000 large digs (8 m across) one more edit cost 3.8 ms of sync and 4.35 ms of GPU: every edit re-hashed, re-indexed and re-uploaded every large brush, and every coarse chunk under them evaluated them all. - Large brushes bake at their bake level (planet::bake_level, the first whose cells they span at most 32 of): sealing bakes them there (EditStore::bake_at), coarser levels follow by majority, and a coarse cell whose children are mostly unchanged keeps its own edit. Below it they stay analytic. At the bake level a cell reads as the analytic brush did. Snapshots are version 3; older ones bake their large brushes on load, under the cells baked after them. - BrushIndex keeps brushes apart by the levels they apply at, so coarse footprints skip the baked ones. - Incremental sync: the view keeps the large layer's faces and index while seals only append to it, diffs the brush lists past their common start, writes the brush buffer's tail in place, and extends the brick index by a seal's columns. - A GPU brick cache (64 MB, least recently used reused): regenerated chunks send their brick tables, not their bricks again; a uniform brick is its table entry. At 10000 large digs: sync 0.51 ms, update CPU 0.48 ms, generation GPU 0.37 ms (0.34, 0.30, 0.20 at 100). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An edited chunk the plan evicts is packed on the GPU before its pages go back (pack_main: its meta, mids, each leaf page's leaves, materials and normals, its detail pages; 12 KB on average) and read back, kept on the CPU (Capacity::kept_bytes, least recently used dropped). A kept node the plan wants again is restored by copy (restore_main: new pages off the free stacks, its result written as a build's), without a footprint, the terrain program or the brushes over it. An edited chunk that comes out empty is kept as such: the plan, a few frames behind its results, asked for dug chunks it had just found empty, each generated again from the history. Edits over kept nodes drop them. edited_chunks_come_back_by_copy: a site under 2000 large digs, left and seen again: 1041 edited chunks restored and none generated (534 without keeping), 58 ms of generation GPU against 308, every pixel as before; an edit over restored chunks draws exactly, and comes back with them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before saving, ChunkWorld::keep_resident packs the resident edited chunks too, and kept_snapshot writes every kept chunk: a header (a fingerprint of the generation shaders, terrain constants and world, and the history's length and hash) and the packs deflated. Planet::snapshot_with_chunks carries them as an opaque section of the edit snapshot (version 4); from_snapshot_with_chunks gives them back and load_kept keeps them in a new world for exactly that history, so it restores what the history made instead of generating it. a_loaded_world_restores_its_edited_chunks: the dug site saved (313 chunks, 316 KiB besides the snapshot's 260) and loaded: 1172 edited chunks restored, none generated (313 without them), 90 ms of generation GPU against 234, every pixel as saved; another history is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kept chunks are saved beside the edit snapshot, not in it: a host folds only its oldest edits into a snapshot (Pulsar lists its newest 4096), and the chunks are of every edit. The snapshot's version 4 chunk section goes. ChunkWorld::kept_chunks takes the kept chunks sharing their packs (cheap on the render thread); KeptChunks::to_bytes deflates them on the saving thread and from_bytes reads them back; load_kept takes them, for exactly their history. PlanetFrame gains kept (chunks for a world built for its planet; ChunkPass::kept_loaded says how many, or why they were refused) and export (KeptExport: a save asks, the pass packs the resident edited chunks over the next frames, asking for frames meanwhile, and answers with the planet shown and its kept chunks). VoxelEditJournal keeps a renderer's chunks of its edits (VoxelKeptChunks: the bytes and the journal length and hash they are for), saved as "kept" and dropped by any change of the edits. saved_edited_chunks_come_back_through_the_frame: 111 chunks exported in 6 frames; loaded, 84 edited chunks restored and none generated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tuafo
added a commit
to Far-Beyond-Pulsar/Pulsar-Native
that referenced
this pull request
Oct 17, 2026
A level save now keeps the voxel planet renderer's chunks of the terrain's edits with its journal, so a loaded level copies what the edits made instead of generating it from the terrain and every edit. - HelioRenderer owns a VoxelChunkSaves (request/answer, condvar), given to the planet backend and to the editor through HelioEditorMailbox; the level editor keeps it in SceneDomain.voxel_chunk_saves. - save_blocking, after compacting the journals, asks for the chunks when a terrain with edits keeps none (keep_voxel_chunks, waiting up to 5 s); the backend has its pass pack them (PlanetFrame.export), answers for the journal its shown planet was built from, and the save deflates them off the render thread and keeps them only while the journal is unchanged. - A rebuilt world parses the journal's kept chunks on the world builder thread and passes them in PlanetFrame.kept; the pass loads them into the new world. Helio 1dcc418f (Far-Beyond-Pulsar/Helio#353): kept chunks travel with the journal and the frame, not the edit snapshot. Tests: voxel_kept_chunks (90 chunks, 105 KiB, answered in 7 frames; a new renderer loads all 90) and a_level_save_keeps_the_renderers_voxel_chunks (the editor's save while the viewport draws; the level loads with them). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nine changes to the voxel planet pass, plus a merge of
main(crates/passes/3d/helio-pass-voxel-planet). The design and every measurement are indocs/chunk-octree.md.Pulsar pins this branch in Far-Beyond-Pulsar/Pulsar-Native#1395 (draft). Merge this PR first, then re-pin Pulsar to Helio
main.3D chunk octree with ray-guided residency (
f866cbb6)(face, level, cx, cy, cz). Classification accounts for what a node's descendants hold (caves, brushes at any size, baked bricks), so a coarse node never hides finer detail.Docs: corrupt captures are an NVIDIA driver fault (
955e7cf5)The occasional captures with 8x8 haze blocks are not an engine race. Only NVIDIA's Vulkan driver shows them (616.64, RTX 3060): D3D12 never does, and synchronization validation finds no hazard. Reported to NVIDIA as bug 6939141. The docs list what was tried and what changes it.
Per-voxel materials for edited cells, and a brush index (
cdaf7714)PickHithas a newmaterialfield. Pulsar'sPickHitconstructor invoxel_backend.rsneedsmaterial: 0when its Helio submodule is bumped.BrushIndex(a loose grid per face) replaces the scan of every brush per chunk inqueue_footprint.Measurements
mountain_slopehold, primary rays (mean of 4 rounds)Without the per-slot flag, the detail lookup cost +0.9% on primary rays.
Generation materials match
Planet::material(e5398492)The new report test
natural_cells_against_the_leaf_rule(ignored by default) compared drawn materials withPlanet::materialon natural mountain ground and in dug pit walls. Before this commit, 40.5% of pixels differed (0% on flat grassland), mostly from a CPU/GPU mismatch.ground_materialat the column's domain point;Planet::materialuses the cell's volume point. The snowline's noise decorrelates 2 km up, so whole views flipped (snow drawn as stone). The slope estimates also differed.Planet::material_slope. Each computed footprint evaluates a lattice of level-4 heights, through three dispatches, and keeps the block slopes in the footprint pool.uniform_leaves_draw_canonical_materials. On leaves whose 16 columns all top out in them with one material, the top faces draw the canonical material exactly: 31,710 pixels, none wrong (779 wrong before).Dug walls draw their cells' own materials (
c0f1779c)Planet::material.dug_walls_draw_canonical_materials: all 57,050 wall and floor pixels of pits at three varied spots drawPlanet::material, at about 170 detail pages per pit.GPU test worlds sized to fit VRAM (
4f11cf98)ChunkWorld::with_capacitytakes aCapacity(chunk slots, detail pages, footprint pool slots).newkeeps the game default and its environment variables. Tests use 16,384 chunk slots, about 1.1 GB each.Large brushes into data: edits on data, step 1 (
acfcf426)At 10,000 large digs 8 m across, one more edit cost 3.8 ms of CPU sync and 4.35 ms of GPU generation:
The fixes:
At 10,000 large digs, one more edit now costs 0.51 ms of sync, 0.48 ms of update CPU and 0.37 ms of GPU generation, against 0.34, 0.30 and 0.20 ms at 100.
Edited chunks kept across eviction: edits on data, step 2 (
725f4c88)New gate,
edited_chunks_come_back_by_copy: a site under 2,000 large digs, left for a far view and then seen again.An edit over restored chunks draws exactly, and it comes back with them after another round trip.
Kept chunks in saves (
41bdb45e,1dcc418f)keep_resident()also packs the resident edited chunks.kept_chunks()then takes every kept chunk; it shares the packs, so it's cheap on the render thread.KeptChunks::to_bytes()deflates them (about 1 KB per chunk) on the saving thread.41bdb45eis removed; snapshots stay at version 3.VoxelEditJournalstores a renderer's chunks for its edits asVoxelKeptChunks: the bytes, plus the journal length and hash they were made for. They're saved askept, and any change to the edits drops them.KeptChunks::from_bytes()reads the chunks back andload_kept()refills a new world's cache. It checks a fingerprint of the generation code, terrain and world, and refuses chunks saved for a different edit history.PlanetFrame.keptgoes into any world built for that planet;ChunkPass::kept_loadedreports how many loaded, or why they were refused.PlanetFrame.export(KeptExport) is a save's request: the pass packs the resident edited chunks over the next frames, asking for frames meanwhile, then answers with the planet shown and its kept chunks.Gate
a_loaded_world_restores_its_edited_chunks: the dug site is saved (313 chunks, 316 KiB on top of the snapshot's 260) and loaded into a new world:New
saved_edited_chunks_come_back_through_the_frame(voxel_pass_graph): 111 chunks exported in 6 frames. Once loaded, 84 edited chunks are restored and none generated. Far-Beyond-Pulsar/Pulsar-Native#1395 wires this into the editor's save and load.Merge of
main(b451b343)main's voxel debug views were written against the old tile-quadtreechunks/mod.rs, which this branch replaced. They're ported onto the octree files:PickHit.materialnow reads five;Tests
cargo test --release -p helio-pass-voxel-planetat default threads: 75 unit tests and 24 GPU tests pass.helio-default-graphs:voxel_pass_graph(9 tests) passes.environment_joinandscene_join's camera-relative tests fail exactly as they do onmain; this branch changes nothing outside the voxel crate and itsvoxel_flightexample.edited_cells_draw_their_own_materials. All 16,306 edited-cell pixels match; with the lookup disabled, 14,610 of them are wrong.the_brush_index_finds_what_a_scan_finds,kept_chunks_stay_with_exactly_their_edits(helio-voxel-data, 23 tests pass).uniform_leaves_draw_canonical_materials,dug_walls_draw_canonical_materials,edited_chunks_come_back_by_copy,a_loaded_world_restores_its_edited_chunksandsealed_large_brushes_read_the_same_at_their_bake_level(unit).🤖 Generated with Claude Code