From 530956889b7421e98968a4733c222730f2b4c0df Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Wed, 30 Sep 2026 15:24:23 +0300 Subject: [PATCH] render3d: a README note on painted ground layers, and the changeset Co-Authored-By: Claude Opus 5.5 --- changes/ground-painted-zones.md | 13 ++++++++++++ packages/ludic.render3d/README.md | 35 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 changes/ground-painted-zones.md create mode 100644 packages/ludic.render3d/README.md diff --git a/changes/ground-painted-zones.md b/changes/ground-painted-zones.md new file mode 100644 index 00000000..90e2ec0a --- /dev/null +++ b/changes/ground-painted-zones.md @@ -0,0 +1,13 @@ +bump: minor +type: feat +**`ludic.render3d`: painted ground layers grow solid things, with ids, and the trample is data.** +`ground_fill`'s candidate is its own function, `ground_candidate` (pure `gf_*` steps with the density read +between them, into a caller-held `GroundCand`), and `ground_fill` draws exactly its answers - the same +operations in the same order as before, so every cover layer grows bit for bit what it did. A layer with +`solid: true` is filled at `step0` with band 0's hashes whatever the camera, a far band drawing a stable +subset; each thing has an int id from (layer, chunk, cell) (`ground_solid_id`), and `ground_solid_list` / +`ground_solid_at` answer a chunk's things or one by id for physics, the nav bake and saves. +`r3d_ground_clearing(x, z, r_in, r_out, floor)` hands the trample over as discs the editor can see, beside +the `r3d_on_ground_trample` callback, which still works. `tests/ground_fill_test.ludic` holds all of it to +`tests/ground_fill_golden.json` (written by `tests/gen/ground_fill_golden.ludic`), the file the studio's +TypeScript generator is tested against. diff --git a/packages/ludic.render3d/README.md b/packages/ludic.render3d/README.md new file mode 100644 index 00000000..9c0f10a8 --- /dev/null +++ b/packages/ludic.render3d/README.md @@ -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.