feat(shared): collapse LOD occupancy by majority fill over 2x2x2 groups #5

Merged
Serkyo merged 7 commits from feat/lod-occupancy-collapse into dev 2026-09-15 15:11:22 +00:00
Owner

What does this PR do?

I added crates/shared/src/world/lod.rs, a new private module re-exported from crates/shared/src/world.rs as COLLAPSE_CHILDREN, OccupancyGrid, and collapse_occupancy.

collapse_occupancy takes the eight solid-or-empty flags of a 2x2x2 group and returns the occupancy of their parent cell. It counts the solid children and returns true at four or more, so the halfway threshold is inclusive. It is const fn and uses an index loop rather than an iterator because iterator adapters are not usable in const context.

OccupancyGrid is the cubic grid those flags live in: a size extent and a Box<[bool]> of size * size * size cells. index lays them out x-major, y-minor, matching the order Chunk already stores voxels in, so a grid built from a chunk needs no transposition. new allocates an empty grid, from_cells wraps an existing Vec<bool> and returns None when the length does not match the extent, and get/set address single cells.

OccupancyGrid::collapse produces the next coarser tier. It returns None when the extent is zero or odd, since neither has a whole 2x2x2 partition, and otherwise walks every parent cell, gathers its eight children by treating the loop counter's three low bits as the x, y, and z offsets within the group, and writes collapse_occupancy of them into the coarser grid.

crates/shared/src/tests/world_lod.rs holds eight tests. The first is exhaustive over all 256 child arrangements, which pins the rule to the solid count and proves the outcome cannot depend on which particular children are solid.

Why is this change necessary?

The LOD ladder collapses a 2x2x2 group of tier-N cells into one tier-N+1 cell at every step, giving 8x the volume per step to match the distance-doubling tiers. Deciding whether that parent cell is solid is the first thing a step has to do, and nothing in the workspace did it yet, so the ladder had no data layer to stand on. This is the occupancy decision on its own, as a pure function over flags, with no chunk or worldgen coupling.

Two constraints drove the shape of it. Averaging occupancy into a fraction and thresholding late is what produces the blobby, muddy look distant-LOD systems are known for, so the collapse yields a flag and the tiers stay binary. The test also has to run against the immediate children at each step rather than against the original 0.5 m voxels, which is what lets a thin feature that survives one step stay eligible to survive the next. Both are structural, and unwinding either once the mesher and the streaming path consume this output would be expensive.

Scope of Changes

shared

Testing

Automated. Everything below was run on this branch with a clean working tree:

  • cargo check -p shared: passed.
  • cargo test -p shared: 226 passed, 0 failed, including the eight new world::lod::tests.
  • cargo clippy --all-targets --all-features -- -D warnings: passed across all six crates.
  • cargo fmt --all -- --check: passed.

No Lua changed on this branch, so selene and stylua were not run.

Manual. There was no in-game verification. Nothing on this branch reaches the renderer or the network path, so there is nothing to observe at runtime yet. The exhaustive 256-arrangement test is the substitute, and it covers the rule's whole input domain.

Additional Context

The intended rule has a second half that is not implemented here, and I want that called out rather than discovered in review. A parent is meant to be solid if either any child on the parent's outward-facing boundary shell is solid (the silhouette rule, which biases toward keeping thin features like ridge lips and cliff edges) or child occupancy reaches 50% (majority fill, which handles bulk volume). Only the majority fill half is here.

The silhouette half does not resolve at this scale. It splits children into a boundary shell and an interior, and in a 2x2x2 group all eight children are corners, so the shell is the entire group and the interior is empty. Taken literally it would make any group with a single solid child solid, which fattens isolated specks into floating blobs and is the failure the OR of the two rules exists to avoid. Either the shell formulation assumes a larger collapse factor, or it needs restating in terms of the parent's own neighbours instead of its children. That is a design question rather than an implementation one, so I left it open instead of picking an interpretation in code. The module doc records the omission.

collapse_occupancy is const fn, which makes the threshold checkable at compile time by later code that wants to specialise on it.

The 50% threshold is a starting point and will need tuning against a profile once there is something to look at. It lives in one private constant, MAJORITY_FILL_THRESHOLD, so moving it is a one-line change.

No response

Checklist

  • I have branched from dev (or a feature branch off dev) and my PR targets dev.
  • I have kept my changes focused to a single concept.
  • I have added or updated documentation (/// doc comments for Rust) where necessary.
  • I have tested my changes and described any relevant automated or manual testing above.
### What does this PR do? I added `crates/shared/src/world/lod.rs`, a new private module re-exported from `crates/shared/src/world.rs` as `COLLAPSE_CHILDREN`, `OccupancyGrid`, and `collapse_occupancy`. `collapse_occupancy` takes the eight solid-or-empty flags of a 2x2x2 group and returns the occupancy of their parent cell. It counts the solid children and returns true at four or more, so the halfway threshold is inclusive. It is `const fn` and uses an index loop rather than an iterator because iterator adapters are not usable in const context. `OccupancyGrid` is the cubic grid those flags live in: a `size` extent and a `Box<[bool]>` of `size * size * size` cells. `index` lays them out x-major, y-minor, matching the order `Chunk` already stores voxels in, so a grid built from a chunk needs no transposition. `new` allocates an empty grid, `from_cells` wraps an existing `Vec<bool>` and returns `None` when the length does not match the extent, and `get`/`set` address single cells. `OccupancyGrid::collapse` produces the next coarser tier. It returns `None` when the extent is zero or odd, since neither has a whole 2x2x2 partition, and otherwise walks every parent cell, gathers its eight children by treating the loop counter's three low bits as the x, y, and z offsets within the group, and writes `collapse_occupancy` of them into the coarser grid. `crates/shared/src/tests/world_lod.rs` holds eight tests. The first is exhaustive over all 256 child arrangements, which pins the rule to the solid count and proves the outcome cannot depend on which particular children are solid. ### Why is this change necessary? The LOD ladder collapses a 2x2x2 group of tier-N cells into one tier-N+1 cell at every step, giving 8x the volume per step to match the distance-doubling tiers. Deciding whether that parent cell is solid is the first thing a step has to do, and nothing in the workspace did it yet, so the ladder had no data layer to stand on. This is the occupancy decision on its own, as a pure function over flags, with no chunk or worldgen coupling. Two constraints drove the shape of it. Averaging occupancy into a fraction and thresholding late is what produces the blobby, muddy look distant-LOD systems are known for, so the collapse yields a flag and the tiers stay binary. The test also has to run against the immediate children at each step rather than against the original 0.5 m voxels, which is what lets a thin feature that survives one step stay eligible to survive the next. Both are structural, and unwinding either once the mesher and the streaming path consume this output would be expensive. ### Scope of Changes shared ### Testing Automated. Everything below was run on this branch with a clean working tree: - `cargo check -p shared`: passed. - `cargo test -p shared`: 226 passed, 0 failed, including the eight new `world::lod::tests`. - `cargo clippy --all-targets --all-features -- -D warnings`: passed across all six crates. - `cargo fmt --all -- --check`: passed. No Lua changed on this branch, so `selene` and `stylua` were not run. Manual. There was no in-game verification. Nothing on this branch reaches the renderer or the network path, so there is nothing to observe at runtime yet. The exhaustive 256-arrangement test is the substitute, and it covers the rule's whole input domain. ### Additional Context The intended rule has a second half that is not implemented here, and I want that called out rather than discovered in review. A parent is meant to be solid if *either* any child on the parent's outward-facing boundary shell is solid (the silhouette rule, which biases toward keeping thin features like ridge lips and cliff edges) *or* child occupancy reaches 50% (majority fill, which handles bulk volume). Only the majority fill half is here. The silhouette half does not resolve at this scale. It splits children into a boundary shell and an interior, and in a 2x2x2 group all eight children are corners, so the shell is the entire group and the interior is empty. Taken literally it would make any group with a single solid child solid, which fattens isolated specks into floating blobs and is the failure the OR of the two rules exists to avoid. Either the shell formulation assumes a larger collapse factor, or it needs restating in terms of the parent's own neighbours instead of its children. That is a design question rather than an implementation one, so I left it open instead of picking an interpretation in code. The module doc records the omission. `collapse_occupancy` is `const fn`, which makes the threshold checkable at compile time by later code that wants to specialise on it. The 50% threshold is a starting point and will need tuning against a profile once there is something to look at. It lives in one private constant, `MAJORITY_FILL_THRESHOLD`, so moving it is a one-line change. ### Related Issues _No response_ ### Checklist - [x] I have branched from `dev` (or a feature branch off `dev`) and my PR targets `dev`. - [x] I have kept my changes focused to a single concept. - [x] I have added or updated documentation (`///` doc comments for Rust) where necessary. - [x] I have tested my changes and described any relevant automated or manual testing above.
feat(shared): collapse LOD occupancy by majority fill over 2x2x2 groups
Some checks failed
Auto Labeler / label-scope (pull_request_target) Successful in 4s
CLA Signed All authors have signed the CLA.
CLA Check / cla-check (pull_request_target) Successful in 3s
CI / Dependency Licenses & Advisories (pull_request) Has been cancelled
CI / Lua Lint & Format (pull_request) Has been cancelled
CI / Commit Message Lint (pull_request) Has been cancelled
CI / LFS Pointer Guard (pull_request) Has been cancelled
CI / Rust Check, Lint & Test (pull_request) Has been cancelled
ff4b5811fc
fix(shared): bounds-check occupancy cell coordinates in debug builds
Some checks failed
CLA Signed All authors have signed the CLA.
CLA Check / cla-check (pull_request_target) Successful in 3s
CI / Dependency Licenses & Advisories (pull_request) Has been cancelled
CI / Lua Lint & Format (pull_request) Has been cancelled
CI / Commit Message Lint (pull_request) Has been cancelled
CI / LFS Pointer Guard (pull_request) Has been cancelled
CI / Rust Check, Lint & Test (pull_request) Has been cancelled
Auto Labeler / label-scope (pull_request_target) Successful in 3s
c42f834856
Merge branch 'dev' into feat/lod-occupancy-collapse
Some checks failed
CLA Signed All authors have signed the CLA.
CLA Check / cla-check (pull_request_target) Successful in 3s
CI / Rust Check, Lint & Test (pull_request) Successful in 41m33s
CI / Dependency Licenses & Advisories (pull_request) Failing after 26s
CI / Lua Lint & Format (pull_request) Successful in 8s
CI / Commit Message Lint (pull_request) Successful in 3s
CI / LFS Pointer Guard (pull_request) Successful in 6s
ba7f7c82f9
Merge branch 'feat/lod-occupancy-collapse' of ssh://git.cryoforgenexus.com:2222/Synvael/synvael into feat/lod-occupancy-collapse
Some checks failed
CLA Check / cla-check (pull_request_target) Has been cancelled
CI / Rust Check, Lint & Test (pull_request) Successful in 42m21s
CI / Dependency Licenses & Advisories (pull_request) Successful in 26s
CI / Lua Lint & Format (pull_request) Successful in 7s
CI / Commit Message Lint (pull_request) Successful in 3s
CI / LFS Pointer Guard (pull_request) Successful in 5s
6e561468e1
Merge branch 'dev' into feat/lod-occupancy-collapse
All checks were successful
CLA Signed All authors have signed the CLA.
CLA Check / cla-check (pull_request_target) Successful in 3s
CI / Rust Check, Lint & Test (pull_request) Successful in 35m58s
CI / Dependency Licenses & Advisories (pull_request) Successful in 27s
CI / Lua Lint & Format (pull_request) Successful in 7s
CI / Commit Message Lint (pull_request) Successful in 2s
CI / LFS Pointer Guard (pull_request) Successful in 6s
80c4918b87
Serkyo deleted branch feat/lod-occupancy-collapse 2026-09-15 15:11:22 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Synvael/synvael!5
No description provided.