The first cut (LGD1) stored every tile of every layer raw, scale channel and all: 288 MB a map, which
made Maroon's pack 803 MB. LGD2 keeps a tile index per layer (8 bytes a tile, read whole at the map's
load): 0 all zero, 1 one value in the word, else an offset and a length, the top bit set when the bake
deflated it (inflated into the tile cache with the runtime's z_inflate, a reused buffer; nothing per
frame beyond the cache). A layer keeps its scale only where it varies, the density 6 bits and the scale
4 (the same integer arithmetic as the bake). The bake is maroon-lake's tools/bake/ground_density.py;
the dev copy here writes the same format stored. Maroon 23.2 MB, Lamar 17.2 MB.
The deflated flag is tested as a negative length: `b & 0x80000000` compiled to broken IR (an i32
`and` with a bare `-`), a compiler fault to fix separately.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GroundFill gains scale_var (an instance's scale x (1 +- var), from its own hash) and band_grow
(x (1 + grow * band)): a painted density carries the local mean, and without them every flower of a
kind was the same size and the far clumps lost the growth that kept the cover as the step widened.
Both 0: as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the test runner re-makes every state between tests again: since 12fdc07 a state is made by its getter on
first use, so re-running L_init_globals left the last test's state in place (state/tested failed its
second test); @L_reset_states forgets every lazy state, and the runner calls it before each test
- diag_json_case counts errors, and an absent @Ref / @Tint / @OneOf target is a warning since d82dc31:
ref_unknown is 1 error and node_bad 8
- schema_hash.ludic prints 1: a bool is 1 or 0 as text (random_plain's 1 1 1)
- permap_check_case looks for the unit warning without the quotes the JSON escapes
- baked_test's inputs-hash test makes its directory: each test has a temp directory of its own
- ludic deps prints phase 25's five counters too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ground_density_bake(path, key, version, hash, pngs) cuts each layer's PNG (R the chance a cell keeps one,
G an optional scale) into the terrain's 64 m tiles, one layer decoded and let go at a time, as a bake's
file; ground_density_open(...) reads it - or, when it is missing or stale, a dev build's own copy cut from
the PNGs - a tile at a time into a 256-tile clock, never a layer whole. ground_fill(s, cx, cz, band, g)
places a GroundFill row's instances in a stream chunk: a candidate per cell of the band's step, jittered,
kept when a hash of (layer, chunk, band, cell) falls under the density (times the game's trample,
r3d_on_ground_trample, where the row asks); scale, yaw, seed and wind from the same hash. Nothing calls
it yet: behaviour unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The seat height, the horse's walk, gallop, acceleration and turn (VE_HORSE_*), its stamina (a gallop
over 6 m/s spends 6 a second, a walk gives back 3, 5 needed to gallop) and hay (0.02 a metre,
VEHICLE_HAY_MIN), the boat's stroke, turn and wind (VE_ROW, VE_ROW_TURN, VE_WIND) and where a rider
gets off (the horse's 1.2 m flank, the boat's 1.25 m wade) move into VehicleSpec (spec.ludic), held
per kind in VehiclesState.ve_specs and kept across a reset. vehicles_spec(kind, spec) sets one,
vehicle_spec(kind) asks it; a kind with no spec cannot be called. VEHICLE_HORSE and VEHICLE_BOAT stay
the kinds. The tests hand Maroon Lake's numbers in, and a new one holds a spec's walk and seat_h.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A prebuilt tool, bin/ludic-ui-preview (built by ludic-dev build, shipped beside ludic-lsp, run as
`ludic ui-preview [--font DIR]`): ludic.ui with a backend that records every draw call as a line,
and mock component classes the studio describes (load / model / calls). frame t runs ui_show at
interface time t; text is measured on the CPU with the game's font.json metrics; locale goes
through ludic.i18n; tree / box / rules inspect the frame. No game code, no GPU, no network. The wire
protocol is frozen as tools/ui-preview/protocol-v1.md; smoke.txt is a transcript to run.
ludic.ui gains, all additive: UiClass.make_of, UiBackend.said / emitted, UiRule.text / at (with
comment line breaks kept so rule lines count true, and a class's styles read under its .lss path),
and ui_root, ui_find, ui_rules_of, ui_rule_value, ui_building, ui_class, ui_class_load,
ui_file_forget, ui_errors_clear; ui_nine_cuts_into exported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
impostor_from_baked / impostor_refill read a bundle of two DX10 .dds (albedo, normal: BC7_UNORM, every
level) through tex_load_dds_at and gpu_tex_compressed - no gpu_tex_mips - and check each is the
impostor's size; sampled as a painted atlas is (clamped, no anisotropy). No BC on the GPU, or a bake
that does not fit, and the caller paints RGBA8 and makes mips as before; the fog re-open reads the BC7
file again. The handles and every draw are unchanged. Compile-only: nothing run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GrassKind (grass_kind.ludic) carries what grass.ludic, grass_gpu.ludic, grass.vert, grass_cull.comp and
model.frag's BLADE path held as constants: the cell, s0 / d0 / radius, the row bands and rows, the blade
profile (neck, root, swell, tip, bend), the heights, clumps, widths and arch, where it grows (slope, snow,
the photograph's test), the root / tip / gone-to-seed colours, the far tufts, the root occlusion, the
roughness, the sheen and the wind. Its defaults are those constants exactly, so a game that sets nothing
draws as before. The shaders read them as u_gk_* (the cull as five more Params vec4s, 288 bytes);
grass_reset.comp writes each band's index count so a kind of other rows reaches the draw in order.
grass_quality holds the kind to a settings tier (never denser, never farther); R3D_GRASS_* still win.
grass_profile_w / _bend are the one profile, for the meshes and a game's preview. grass.mesh takes only
the cell: the rest of it is an older copy that already differs from grass.vert, left as it draws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ludic.lab: lab_png_write / lab_png_write_from (raw 8-bit, 1-4 channels, stored deflate) in png_write.ludic,
importable alone with its own LabPngState; png_convert.ludic's previews of float textures (R32F min..max,
RG16F x255, HDR x/(1+x) + sRGB); lab_ppm_to_png on the same encoder.
render3d: bake_load.ludic - impostor_from_baked / impostor_source / impostor_refill (a fog re-open reads
the bake), sky_baked_in and sky_precompute trying the bake at the start yaw (sky_compute is the
convolution, and sky_ibl_bytes always uses it), carpet_from_baked / carpet_bytes / carpet_finish,
bake_part_count / _len / _off. Compile-only: nothing run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A package on ludic.base alone. DevlinkNet is the transport (a game binds Udp, the test a fake);
DevlinkWorld's members are the verbs, each defaulting to "not offered" (err unbound): ping, hello (with
Build.schema_hash as 16 hex digits), cam_get / cam_set / cam_release, goto, map_load, time, weather, shot,
pause / resume / step. One request a frame, parsed in place from a fixed 8 KB buffer and answered into
another; map_load, shot and step answer later by id, one at a time, 5 s at most. Loopback senders only,
opened only when enabled() (dev_tools), co-op refuses everything but ping and hello. tests/devlink_test:
each answer byte for byte, bad arguments, a stranger dropped, co-op, the late answers and their timeout,
a closed build that never opens. Compiles; not run (the Master runs the package tests).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gltf_factors reads pbrMetallicRoughness.baseColorFactor, metallicFactor, roughnessFactor and the
emissiveFactor into the primitive (pr.fac), and prim_factors hands them to every program that draws its
textures (actors, the scatter's meshes and levels, the impostor bake) as 1 - factor, so a program never
given them - or a material that gives none (pr.fac null) - multiplies by 1 and draws as before. An
untextured material with a colour (or a metal/roughness) samples a pure white for it, so the factor is
exactly what it draws: horse_cornea is its own colour, not the 200 grey every untextured part had.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both constants were worked out by hand and a byte off each: the tiles' bake wrote "LAAK" and "LDT2", which
ludic bake --check refuses, and r3d_baked_read, holding the same wrong constant, would have refused
ludic.base's correct files (the sun's shadow among them) while accepting its own. Now 0x4B41424C and
0x3254544C, checked against the strings; the unused LTT1 constant is gone. A re-bake is needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A world swap unmounts the old map pack before terrain_reload, and the tiles' file (a baked file in that
pack) stayed open until terrain_unload. terrain_tiles_close closes it and stops the page pool; the next
map's terrain_from_baked / terrain_reload opens its own and makes the pool again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game names the tiles' and the shadow's bake files (key, version) before the map is made; r3d_init's
terrain step and terrain_reload then open them with terrain_from_baked / terrain_shadow_from_bytes and
generate nothing, and fall back to the survey (saying so once) when a bake is missing or stale. Without
the call nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tiles' photograph side is 0 when the map has none: no photograph or coarse photograph sections, no
decode, ter_o answers 0, and no coarse photograph texture - as such a map has always drawn. The cut and
terrain_tiles_bake no longer require one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
baked_open refuses a header whose key is not followed by a zero byte; a key a multiple of 8 long got
none. Now the payload starts at 32 + ((len(key) + 1 + 7) / 8) * 8, as bake_write writes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stream_capture(s, cx, cz, band) points the stream at a chunk of its own (made on the first capture),
calls the registered r3d_stream_fill for that cell and band, and returns how many instances it
emitted; stream_captured(i, k) reads them from stream_scratch. Build-time only; nothing in a frame
calls it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
baked_tex.ludic reads ludic.base's baked form by hand (render3d uses no package): the LBAK header, the
key ("png_sheets" / "cutouts", the Bakes rows) and the generator version, then the payload -
w, h, channels, depth and the samples for a PNG (the caller frees them as it would a decode), a whole
.dds for a cut-out (tex_load_dds_at: a .dds at an offset). Missing, or another key or version, and the
PNG path runs as before. Compiles with Maroon Lake (game, lab, lab/bake/textures).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prec_put / prec_get / prec_size (Physics' spec): PREC_FULL, 12 bytes, for trees, boulders and stones
(u16 x, z in 1/65536 of the chunk; u16 y in cm above y_base; u8 scale over the kind's lo..hi, yaw, seed,
wind), and PREC_PACKED, 7 bytes, for the stream kinds (56 bits, least significant first: x 12, z 12,
y 12 in cm, scale 6, yaw 6, seed 4, wind 4). An encode takes the cell a value falls in, a decode its
centre, so a round trip is stable; a record is read from a []byte at an offset, never a pointer.
stream_emit_baked and layer_chunk_put take (recs, at, fmt) and decode through it; the earlier sb7_*
pair is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>