render3d: a README note on painted ground layers, and the changeset

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-30 15:24:23 +03:00
parent 54e267dfa6
commit 530956889b
2 changed files with 48 additions and 0 deletions

View file

@ -0,0 +1,35 @@
# ludic.render3d
A physically based 3D renderer over Vulkan. `FEATURES.md` lists what is implemented and where.
## Painted ground layers
A ground layer is a density painted over the map (R the chance a cell keeps one, G its scale, 2 m texels
baked into an LGD2 file, `ground_density.ludic`) and a `GroundFill` row of numbers. Everything it grows is
a pure function of those two - the same on every machine and in the editor's preview.
- **The candidate** (`ground_cand.ludic`): for (layer, chunk, band, cell) `ground_candidate` answers
whether one stands there and its x, z, scale, yaw, seed and wind, into a `GroundCand` the caller holds.
Its steps are pure: `gf_along` (the jitter, draws 0 and 1), `gf_keeps` (draw 2 under the chance),
`gf_scale` (G and draw 6), `gf_yaw` (3), `gf_seed` (4), `gf_wind` (5). `ground_fill` draws exactly
these. Numbers are IEEE single precision, `Math.lerp` is `a + (b - a) * t`, `gf_hash` is 32-bit
integer arithmetic with a logical `>>`, and the model shader turns local (x, z) to
`(c x + s z, -s x + c z)` - three.js's rotation about +Y.
- **Cover and solid.** A cover layer (grass, flowers, pebbles) is filled at its band's step, so a far
band is a different, sparser sample. A layer with `solid: true` (rocks, trees) is filled at `step0`
with band 0's hashes whatever the band, and a far band draws the subset whose draw 7 falls under
`(step0 / step_band)^2`; its scale ignores `band_grow`. Nothing solid moves with the camera.
- **Solid ids** (`ground_solid.ludic`): `ground_solid_id(layer, cx, cz, i, j, cells)` =
`((layer * 256 + cz + 128) * 256 + cx + 128) * 512 + i * cells + j`, a positive 31-bit int, stable
while the painting under it is. It holds chunks -128 .. 127 about the stream's corner and at most 22
cells a chunk side (a step of at least 32 / 22 m on 32 m chunks); a cell past that has no id and is
not grown. `ground_solid_list` fills a caller's `GroundSolids` (made once, `ground_solids_new`) with a
chunk's things for physics, the nav bake or a save; `ground_solid_at` answers one by id.
- **The trample as data** (`ground_clear.ludic`): `r3d_ground_clearing(x, z, r_in, r_out, floor)` hands
over a clearing - `floor` inside `r_in`, back to full at `r_out` on a smoothstep, the shape of the
camp's ring and the town's yard. A layer with `trample` multiplies its chance by the game's callback
(`r3d_on_ground_trample`, kept for now) and then by every clearing. A game should use one or the other.
- **Conformance**: `tests/ground_fill_test.ludic` holds the package to `tests/ground_fill_golden.json`
(gf_hash, lerp, yaw, one cover chunk and one solid chunk over a hand-written density), which the
studio's own test reads. After a deliberate change, rebuild it from the repository root:
`bin/ludic run packages/ludic.render3d/tests/gen/ground_fill_golden.ludic --headless`, and commit it.