ludic/packages/ludic.render3d
2026-09-30 17:21:21 +03:00
..
lib/macos-arm64 runtime (macOS): MoltenVK opened first, so a process holds one - the SDK's loader in /usr/local/lib loaded its own MoltenVK beside the linked one and the program drew through it; the loader only when a layer is asked for, pinned to ours (VK_DRIVER_FILES, lib/macos-arm64/MoltenVK_icd.json) unless a driver is named. steady's stream round: a 300-cell warm-up, then the least of three 150-cell windows 2026-09-30 00:41:27 +03:00
native feat(render3d): 22.10 - ludic.render3d carries MoltenVK, so a Mac program has its Vulkan driver 2026-09-28 01:16:26 +03:00
shaders Merge grass/far into main (gathering every branch; main's side kept on conflicts) 2026-09-30 17:21:20 +03:00
tests render3d: ground_fill_golden.json is valid JSON (the cover and solid blocks each closed one brace too many) 2026-09-30 15:36:08 +03:00
actor.ludic render3d: a glTF material's factors are drawn - base colour, roughness, metallic and emission 2026-09-29 18:52:42 +03:00
assets.manifest feat(gl): OpenGL 4.1 and the ludic.render3d renderer 2026-09-10 03:31:12 +03:00
bake_load.ludic render3d: an impostor's bake is BC7 with its mips - both atlases uploaded compressed, as baked 2026-09-29 19:18:48 +03:00
bake_parts.ludic render3d + lab: bakes as PNGs, and impostors, the sky's light and the carpet read from their bakes 2026-09-29 19:10:06 +03:00
baked_tex.ludic render3d: baked textures before the PNG - a png_decode takes assets/baked/png, a cut-out load assets/baked/cutouts 2026-09-29 17:30:57 +03:00
camera.ludic render3d: the fog wall culls on the CPU - nothing past it costs a draw, a vertex or a caster 2026-09-29 15:38:00 +03:00
daylight.ludic packages: ludic.ui's render3d backend threads Render3dState; render3d's settable vars (r3d_dem_path, post_*, grass_*, wt_wade_*, ...) are Render3dState's fields again, not lets 2026-09-25 17:11:15 +03:00
drawstats.ludic r3d/runtime: allocs, keeps and births at 0 on this side 2026-09-28 20:10:59 +03:00
env.ludic Merge lang/png-jobs into main (gathering every branch; main's side kept on conflicts) 2026-09-30 17:21:21 +03:00
FEATURES.md r3d/runtime: allocs, keeps and births at 0 on this side 2026-09-28 20:10:59 +03:00
fmath.ludic r3d/runtime: allocs, keeps and births at 0 on this side 2026-09-28 20:10:59 +03:00
fog_casters.ludic render3d: under a fog wall the volumetric air is marched only to it, SSGI stops where it is solid, and card casters that fill the wall stop rebuilding 2026-09-29 17:00:27 +03:00
fog_impostors.ludic render3d + lab: bakes as PNGs, and impostors, the sky's light and the carpet read from their bakes 2026-09-29 19:10:06 +03:00
fog_streams.ludic render3d: the uniform ring is 16 MB a half and grows when a frame outgrows it; streamed layers are sized for the fog's reach 2026-09-29 16:22:45 +03:00
gltf.ludic render3d: a glTF material's factors are drawn - base colour, roughness, metallic and emission 2026-09-29 18:52:42 +03:00
gltf_cached.ludic render3d: 671 frame-reachable allocation sites to 161 - declared, split, or made nothing 2026-09-28 15:58:18 +03:00
gltf_release.ludic render3d: @creates / @releases on its handles 2026-09-28 16:58:31 +03:00
gpu.ludic wip: uncommitted work gathered before the branch is merged into main 2026-09-30 17:18:48 +03:00
gpu_fault.ludic render3d: a renderer that cannot draw says so and stops - LUDIC_HOME's leading ~ expanded (an unexpanded one left a run without shaders, logging 27 missing variants and drawing on), the SPIR-V manifest ends with E <count> and a cut or miscounted one is refused, and every such failure is a fault: r3d_open / r3d_load_step fail on it, r3d_fault() / r3d_fault_exit(code) for the game; tests/manifest_test 2026-09-29 22:12:39 +03:00
gpu_manifest.ludic render3d: a renderer that cannot draw says so and stops - LUDIC_HOME's leading ~ expanded (an unexpanded one left a run without shaders, logging 27 missing variants and drawing on), the SPIR-V manifest ends with E <count> and a cut or miscounted one is refused, and every such failure is a fault: r3d_open / r3d_load_step fail on it, r3d_fault() / r3d_fault_exit(code) for the game; tests/manifest_test 2026-09-29 22:12:39 +03:00
gpu_vk.ludic wip: uncommitted work gathered before the branch is merged into main 2026-09-30 17:18:48 +03:00
gpu_vk_bc.ludic feat(render3d): 23.3 - a .dds beside a .png is uploaded BC-compressed with its whole mip chain 2026-09-28 01:23:45 +03:00
gpu_vk_draw.ludic wip: uncommitted work gathered before the branch is merged into main 2026-09-30 17:18:48 +03:00
gpu_vk_layers.ludic render3d: the terrain's GPU maps paged round the camera while the tiles are on 2026-09-29 17:13:11 +03:00
gpu_vk_readback.ludic render3d: gpu_tex_read_all and gpu_tex_write_all flush the frame first - the read-back is a submit of its own and took the image before the draws that fill it (the sky bake's BRDF table was all zeros at one width in three) 2026-09-29 23:18:05 +03:00
gpu_vk_res.ludic Merge grass/far into main (gathering every branch; main's side kept on conflicts) 2026-09-30 17:21:20 +03:00
grass.ludic render3d: heightgen's plain and SMOOTH variants compiled (61 programs) - a smoothed or noise-only terrain had no height field on Vulkan; run-time defines as pure functions (program_defs.ludic) and manifest_test holds every program the source can ask for, literal or computed, to the manifest 2026-09-29 22:30:35 +03:00
grass_bind.ludic render3d: a grass kind - the GPU blades' per-kind numbers are a GrassKind record the game hands over (grass_kind_set), not constants 2026-09-29 19:15:39 +03:00
grass_density.ludic render3d: the blades' density window filled apart from its upload - gb_window_fill (the tiles round the camera's, zeros off the map, the corner) and gb_frame sends it; the same bytes, now readable by a test without a GPU 2026-09-30 02:03:43 +03:00
grass_gpu.ludic wip: uncommitted work gathered before the branch is merged into main 2026-09-30 17:18:48 +03:00
grass_kind.ludic render3d: a grass kind - the GPU blades' per-kind numbers are a GrassKind record the game hands over (grass_kind_set), not constants 2026-09-29 19:15:39 +03:00
grass_rule.ludic render3d: the GPU blades grow by a painted density (a layer of the ground densities, grass_density_layer) where a map has one - a 5x5-tile window round the camera in a storage buffer the cull samples - and by today's rules where not; grass_rule_at is the rules on the CPU for the migration 2026-09-29 21:14:22 +03:00
ground_cand.ludic render3d: ground_fill's candidate is a function of its own; solid layers with stable ids; the trample as clearing discs 2026-09-30 15:23:53 +03:00
ground_clear.ludic render3d: ground_fill's candidate is a function of its own; solid layers with stable ids; the trample as clearing discs 2026-09-30 15:23:53 +03:00
ground_density.ludic render3d: gd_fill tests a deflated tile's top bit as written, (b & 0x80000000) != 0, now that the compiler emits the literal (lang/hex-literal, reseeded) - the b < 0 stand-in goes 2026-09-29 22:20:20 +03:00
ground_density_write.ludic render3d: ground densities as LGD2 - empty tiles free, one-value tiles a word, deflated tiles, quantized 2026-09-29 21:55:28 +03:00
ground_fill.ludic render3d: gf_hash's comment says what it is - a 64-bit mix (the big constants lex as longs, & 0xFFFFFFFF is & -1); deterministic, and narrowing it would move every painted thing 2026-09-30 15:38:39 +03:00
ground_solid.ludic render3d: ground_fill's candidate is a function of its own; solid layers with stable ids; the trample as clearing discs 2026-09-30 15:23:53 +03:00
ground_solid_list.ludic render3d: ground_fill's candidate is a function of its own; solid layers with stable ids; the trample as clearing discs 2026-09-30 15:23:53 +03:00
hooks.ludic wip(0.S3): packages and examples migrated again from their pre-0.S sources in one run 2026-09-25 15:31:34 +03:00
layer_chunks.ludic render3d: place_rec.ludic, the one encoder and decoder of a baked placement 2026-09-29 17:28:11 +03:00
mesh.ludic render3d: @owns on the handles records hold 2026-09-28 17:08:15 +03:00
overlay.ludic render3d/overlay: the font atlas kept as its coverage, one byte a texel 2026-09-29 14:45:02 +03:00
package.ludic feat(render3d): 22.10 - ludic.render3d carries MoltenVK, so a Mac program has its Vulkan driver 2026-09-28 01:16:26 +03:00
place_rec.ludic render3d: place_rec.ludic, the one encoder and decoder of a baked placement 2026-09-29 17:28:11 +03:00
png_jobs.ludic inflate in a caller-owned context, and a PNG decoded in three steps (parked: the boot's PNGs move to build time) 2026-09-29 17:17:20 +03:00
post.ludic render3d: under a fog wall the volumetric air is marched only to it, SSGI stops where it is solid, and card casters that fill the wall stop rebuilding 2026-09-29 17:00:27 +03:00
prof.ludic render3d: start-up declared, ov_nine and the caster test on buffers made once - 362 frame sites to 122 2026-09-28 16:21:51 +03:00
program_defs.ludic render3d: heightgen's plain and SMOOTH variants compiled (61 programs) - a smoothed or noise-only terrain had no height field on Vulkan; run-time defines as pure functions (program_defs.ludic) and manifest_test holds every program the source can ask for, literal or computed, to the manifest 2026-09-29 22:30:35 +03:00
programs.ludic render3d: a renderer that cannot draw says so and stops - LUDIC_HOME's leading ~ expanded (an unexpanded one left a run without shaders, logging 27 missing variants and drawing on), the SPIR-V manifest ends with E <count> and a cut or miscounted one is refused, and every such failure is a fault: r3d_open / r3d_load_step fail on it, r3d_fault() / r3d_fault_exit(code) for the game; tests/manifest_test 2026-09-29 22:12:39 +03:00
quat.ludic r3d/runtime: allocs, keeps and births at 0 on this side 2026-09-28 20:10:59 +03:00
r3d.ludic render3d: ground_fill's candidate is a function of its own; solid layers with stable ids; the trample as clearing discs 2026-09-30 15:23:53 +03:00
r3d_root.ludic render3d: a renderer that cannot draw says so and stops - LUDIC_HOME's leading ~ expanded (an unexpanded one left a run without shaders, logging 27 missing variants and drawing on), the SPIR-V manifest ends with E <count> and a cut or miscounted one is refused, and every such failure is a fault: r3d_open / r3d_load_step fail on it, r3d_fault() / r3d_fault_exit(code) for the game; tests/manifest_test 2026-09-29 22:12:39 +03:00
README.md render3d: a README note on painted ground layers, and the changeset 2026-09-30 15:24:23 +03:00
render.ludic render3d: a renderer that cannot draw says so and stops - LUDIC_HOME's leading ~ expanded (an unexpanded one left a run without shaders, logging 27 missing variants and drawing on), the SPIR-V manifest ends with E <count> and a cut or miscounted one is refused, and every such failure is a fault: r3d_open / r3d_load_step fail on it, r3d_fault() / r3d_fault_exit(code) for the game; tests/manifest_test 2026-09-29 22:12:39 +03:00
safe_api.ludic render3d: cut-out padding on every core - tex_dilate's rows per pass through Job.parallel_for, bytes unchanged 2026-09-29 17:11:21 +03:00
scatter.ludic Merge commit 'e908672' into lang/foundations 2026-09-29 19:22:23 +03:00
shadow.ludic render3d: the survey DEM is let go once the height field is made, and the terrain sun shadow is R32F + RG16F 2026-09-29 16:48:26 +03:00
skin.ludic r3d/runtime: allocs, keeps and births at 0 on this side 2026-09-28 20:10:59 +03:00
sky.ludic render3d + lab: bakes as PNGs, and impostors, the sky's light and the carpet read from their bakes 2026-09-29 19:10:06 +03:00
stream.ludic render3d: a stream can be laid on the baked chunks' grid and filled from their records 2026-09-29 17:25:18 +03:00
stream_baked.ludic render3d: stream_capture runs the game's own fill for a chunk outside any frame, for the bake 2026-09-29 17:34:16 +03:00
streamline.ludic render3d: r3d_fog_wall(dist) - fog that closes to the sky's horizon colour from 0.3 dist to dist, and nothing drawn past dist + 8 m 2026-09-29 15:07:40 +03:00
terrain.ludic Merge grass/far into main (gathering every branch; main's side kept on conflicts) 2026-09-30 17:21:20 +03:00
terrain_baked_boot.ludic render3d: terrain_baked_at - a map's terrain and sun shadow taken from its bakes at start and on a swap 2026-09-29 17:42:16 +03:00
terrain_chunks.ludic render3d: the height field and photograph on the CPU as tiles read from a file (off until the game asks) 2026-09-29 16:53:28 +03:00
terrain_height_file.ludic render3d: the terrain's questions never page - ter_h and ter_o read a resident tile in place and any other through the file scratch (heights and now the photograph, four tiles each, sized when the tiles open, forgotten when they close), so terrain_height, _smooth, terrain_ortho, the chunk heightfields, ground_fill, grass, water and shadow no longer take slots or move the clock. Only the loaders page: tp_fill reads through tt_h_load / tt_n_load / tt_o_load, and terrain_tiles_prefetch as before. terrain_height_file is now terrain_height, kept as the name callers wrote against. No answer changes: every read was already the whole copy's texel. tests/height_file_test: heights and photograph equal the texels written, resident or not; a whole-map sweep of the questions, and a ground layer's fill over the map (a one-layer LGD2 written in the test), leave the slots, their contents, the clock and the read counts unchanged 2026-09-30 02:43:17 +03:00
terrain_ltt2_read.ludic render3d: the terrain's questions never page - ter_h and ter_o read a resident tile in place and any other through the file scratch (heights and now the photograph, four tiles each, sized when the tiles open, forgotten when they close), so terrain_height, _smooth, terrain_ortho, the chunk heightfields, ground_fill, grass, water and shadow no longer take slots or move the clock. Only the loaders page: tp_fill reads through tt_h_load / tt_n_load / tt_o_load, and terrain_tiles_prefetch as before. terrain_height_file is now terrain_height, kept as the name callers wrote against. No answer changes: every read was already the whole copy's texel. tests/height_file_test: heights and photograph equal the texels written, resident or not; a whole-map sweep of the questions, and a ground layer's fill over the map (a one-layer LGD2 written in the test), leave the slots, their contents, the clock and the read counts unchanged 2026-09-30 02:43:17 +03:00
terrain_ltt2_write.ludic render3d: the bake and tile magics are "LBAK" and "LTT2" (they were "LAAK" and "LDT2") 2026-09-29 18:28:11 +03:00
terrain_pages.ludic render3d: the terrain's GPU maps paged round the camera while the tiles are on 2026-09-29 17:13:11 +03:00
terrain_pages_fill.ludic render3d: the terrain's questions never page - ter_h and ter_o read a resident tile in place and any other through the file scratch (heights and now the photograph, four tiles each, sized when the tiles open, forgotten when they close), so terrain_height, _smooth, terrain_ortho, the chunk heightfields, ground_fill, grass, water and shadow no longer take slots or move the clock. Only the loaders page: tp_fill reads through tt_h_load / tt_n_load / tt_o_load, and terrain_tiles_prefetch as before. terrain_height_file is now terrain_height, kept as the name callers wrote against. No answer changes: every read was already the whole copy's texel. tests/height_file_test: heights and photograph equal the texels written, resident or not; a whole-map sweep of the questions, and a ground layer's fill over the map (a one-layer LGD2 written in the test), leave the slots, their contents, the clock and the read counts unchanged 2026-09-30 02:43:17 +03:00
terrain_pages_frame.ludic render3d: the terrain's GPU maps paged round the camera while the tiles are on 2026-09-29 17:13:11 +03:00
terrain_tiles.ludic render3d: the terrain's questions never page - ter_h and ter_o read a resident tile in place and any other through the file scratch (heights and now the photograph, four tiles each, sized when the tiles open, forgotten when they close), so terrain_height, _smooth, terrain_ortho, the chunk heightfields, ground_fill, grass, water and shadow no longer take slots or move the clock. Only the loaders page: tp_fill reads through tt_h_load / tt_n_load / tt_o_load, and terrain_tiles_prefetch as before. terrain_height_file is now terrain_height, kept as the name callers wrote against. No answer changes: every read was already the whole copy's texel. tests/height_file_test: heights and photograph equal the texels written, resident or not; a whole-map sweep of the questions, and a ground layer's fill over the map (a one-layer LGD2 written in the test), leave the slots, their contents, the clock and the read counts unchanged 2026-09-30 02:43:17 +03:00
terrain_tiles_cut.ludic render3d: terrain_height_file - terrain_height's exact bilinear that never pages: a resident tile is read in place, any other from tt_file into four tiles of scratch of its own (in the state's defaults, forgotten when the file closes), so no slot is taken and no recency bumped - a sweep at boot no longer changes which tiles the cache holds, and so what is drawn from it. With the whole copy it is terrain_height. tests/height_file_test: equal to terrain_height over a grid across many tiles, resident and not, at tile corners and past the edge; a whole-map sweep leaves slot_of, tile_in, ref, the heights, the hand and the read counts unchanged 2026-09-30 02:29:55 +03:00
terrain_tiles_open.ludic render3d: terrain_tiles_open_file(path, key, version, half, ox, oz) - a map's baked tiles opened for the CPU questions alone (terrain_height, _file, _smooth, terrain_ortho), with no device and no renderer booted: terrain_from_baked's opening without its coarse level on the GPU. False and nothing changed for another bake's key or version, a missing file, or a whole copy already kept (it answers first). tests/tiles_open_test: a bake written with LBAK's header answers its heights and photograph at a map origin of (300, -120), nothing on a device; the refusals leave no file open, and a bake opened after them answers. The hand-written tiles move to tests/fakes/tiles_file.ludic (payload, raw file, bake, the expected answer at an origin, the cache's snapshot), shared with height_file_test 2026-09-30 03:13:18 +03:00
texture.ludic render3d: baked textures before the PNG - a png_decode takes assets/baked/png, a cut-out load assets/baked/cutouts 2026-09-29 17:30:57 +03:00
texture_dds.ludic render3d: baked textures before the PNG - a png_decode takes assets/baked/png, a cut-out load assets/baked/cutouts 2026-09-29 17:30:57 +03:00
vkmem_report.ludic render3d: the terrain's GPU maps paged round the camera while the tiles are on 2026-09-29 17:13:11 +03:00
water.ludic render3d: the height field and photograph on the CPU as tiles read from a file (off until the game asks) 2026-09-29 16:53:28 +03:00

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.