Commit graph

335 commits

Author SHA1 Message Date
b3076597e5 Merge commit '78c261c' into lang/foundations 2026-09-29 22:55:32 +03:00
31dcc389a2 ludic.ui: ui_mounted(out) - the classes of the components the last frame showed, in tree order and each once, into the caller's list - and ui_model_of(cls), the first mounted instance's model read in place; examples/library/ui_mounted
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:51:33 +03:00
78c261c625 ludic.session: ply_rest_least, the party's rest for the clock (the valley's selftest57 down into the packages): the roster's freed slot reused and ply_leave_all, the nearest player alone, and ludic.shop's week of demand leaving the shared Random stream untouched
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:51:01 +03:00
5107d501f5 Merge commit 'aad3f8c' into lang/foundations 2026-09-29 22:49:24 +03:00
aad3f8ca27 ludic.ui: ui_override(path, text), ui_override_clear(path | "") and ui_overridden(path) - a template's or a stylesheet's text given from outside, its component shown again with its state kept and its own text brought back when let go; lib_text answers a copy of the override, and the dev poll leaves an overridden component alone; examples/library/ui_override
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:48:48 +03:00
5bc2342174 ludic.devlink: the interface's verbs (ui_screen, ui_model, ui_tree, ui_override) through a DevlinkUi port
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:48:34 +03:00
9f07fc8a62 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:30:35 +03:00
2161f28766 ludic.base: bake_expand(inputs, map) and bake_maps(first_input) beside bake_inputs_hash - {map} put, globs expanded in whole-path byte order with dot-names out - so a bake's runner and the check expand a row one way; tests/baked_expand_test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:22:05 +03:00
edc94e49e8 Merge commit 'c3fc896' into lang/foundations 2026-09-29 22:15:58 +03:00
f864f94207 ludic build --check reads the maps only for the package's entry - a partial program (a unit test, a molecule, a bake's runner) lacks the game's constants and cannot judge them; --maps reads them anyway, --no-maps never; ludicc unchanged
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:14:53 +03:00
af90a1a334 @Asset(kind, map): a path under each map's directory - ludicc --check looks for it in every map (a @PerMap row's in its own, a game-wide row's in all), unless @Asset(kind, map, optional); the schema marks it scope map; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:13:14 +03:00
c3fc896dea 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:12:39 +03:00
9cc67fee74 Merge commit '55def86' into lang/foundations 2026-09-29 22:09:59 +03:00
847048825a Merge commit '37338cb' into lang/foundations (itoa of INT_MIN: a 2^31 hex literal in 32-bit arithmetic kept its value); reseeded, bootstrap-cfree fixpoint holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:09:09 +03:00
55def863d3 ludic.lab: 16-bit PNGs (lab_png_write16_from), so a test can write a height map render3d reads back as R16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:08:22 +03:00
aac58ec6fa ui-preview: hold <id or key> <pseudo> - hover, active, focus, focus-visible, checked or disabled held on from the next frame on top of the real input, by the element's key (so through model and load), let go with "" or reset; ludic.ui's held.ludic keeps them and the matcher asks it first; protocol-v1.md and smoke.txt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:04:20 +03:00
37338cb0f7 compiler: the most negative int is emitted as itself - itoa took its digits off -v, which overflows back to itself, so 0x80000000 in an int was written as a bare "-" (invalid IR); digits now come off v as it is, and the buffer holds a long's 20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:00:49 +03:00
4fb1dc0ddc Json.parse decodes an escaped string in one pass into one buffer - joined a character at a time, every shorter copy was kept, and a long escaped text took gigabytes (ui-preview: 6.5 GB in 3 s); protocol-v1.md states the @import path rule and the key a file override answers to
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:53:57 +03:00
49e32f9b47 chunked tables: a key is unique across its map and the slot's own, not interned - a slot keeps row i's key in its own buffer i, rewritten when the slot is refilled (interning ~92k map-unique keys as a player walks would fill the bounded intern table and keep them all); ludicc --check refuses a key written in two of a map's chunk files, naming both; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:43:20 +03:00
0634116d59 Merge commit '7c25c48' into lang/foundations 2026-09-29 21:31:11 +03:00
9186abbbe3 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:14:22 +03:00
9ff219d065 Merge commit '4376ddf' into lang/foundations 2026-09-29 21:13:56 +03:00
4376ddf0ec Json.parse decodes \uXXXX to UTF-8 - a surrogate pair joined, a lone surrogate or bad hex as U+FFFD - and \t \r \b \f, where it dropped the backslash and kept the hex as text; examples/lang/json_unicode checks it byte by byte
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:07:10 +03:00
e6f4685bd0 docs: map-scoped tables in LANGUAGE.md, the maps manifest key, a changeset
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:03:56 +03:00
359de7bfe5 Http.text and Http.header return copies - they handed back the handle's own buffer (and on macOS the response's string), which Http.free released: a text read before the free and used after it was garbage or empty (maroon-lake's maps.json was written with 0 bytes)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:57:44 +03:00
cc24409d87 ludic.wildlife: painted habitats and ranges through the port (habitat_in, range_at), the rule where none is painted
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:35:13 +03:00
668a9ecf69 vehicles: a kind's numbers are a VehicleSpec the game hands over (vehicles_spec), not constants
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>
2026-09-29 20:22:02 +03:00
dd25267502 templates: {s} with one string hole and nothing else is a new string, not s itself - it was the one template that passed its string through (string() does), and code that wrote it as a copy kept what it then freed; the mark rides the callee, since escape analysis writes the call's uns; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:14:58 +03:00
e74656c372 Merge commit '49b5e80' into lang/foundations 2026-09-29 20:05:26 +03:00
49b5e80af5 ludic ui-preview: ludic.ui components previewed for a studio over stdio (R5)
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>
2026-09-29 19:53:26 +03:00
281ebc23e1 ludic schema: a components section and a natives list (R4)
Each UI component: module, place, doc, xml and lss paths, props and state
(type, default as written, place, doc), states_read (the header's states),
derived fields with their types, functions and events as the template calls
them (states and instance stripped), and the native tags its template uses.
natives: every ui_native / ui_native_input call with a literal tag - tag, via,
handler, place. schema_version stays 1; the lists are additions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:36:04 +03:00
28f7b4666b Merge commit 'e908672' into lang/foundations 2026-09-29 19:22:23 +03:00
e53a9fc865 Merge commit '3f27235' into lang/foundations 2026-09-29 19:22:23 +03:00
bda7489fbe Merge commit 'f66b912' into lang/foundations 2026-09-29 19:22:22 +03:00
abc78baad8 Merge commit '81cb992' into lang/foundations 2026-09-29 19:22:22 +03:00
3f2723582f render3d: an impostor's bake is BC7 with its mips - both atlases uploaded compressed, as baked
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>
2026-09-29 19:18:48 +03:00
e908672034 render3d: a grass kind - the GPU blades' per-kind numbers are a GrassKind record the game hands over (grass_kind_set), not constants
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>
2026-09-29 19:15:39 +03:00
f66b912cf4 Udp.open_local(port): a socket bound to 127.0.0.1 alone (udp.ll, udp_win.ll), for a port only this machine's tools reach - a dev link - where Udp.open binds every interface
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:15:30 +03:00
81cb992fad attributes: @Material(field) resolved as @Node is; @OneOf on a string field takes words, and every registry row's value is checked against them - words on another field, or constants on a string, is an error; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:13:32 +03:00
6c690c5db6 render3d + lab: bakes as PNGs, and impostors, the sky's light and the carpet read from their bakes
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>
2026-09-29 19:10:06 +03:00
18755d9057 ludic.devlink: an editor's live link into a running dev build (protocol v1, stage 1)
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>
2026-09-29 19:09:59 +03:00
dacc0f7280 Build.schema_hash(): FNV-1a 64 of the program's own schema (the bytes ludic schema prints), worked out only when a program names it, 0 under ludicc --release, which ludic bundle now passes (Mac and Windows); reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:05:28 +03:00
b5bffe99fa attributes: @Tint(SLOT) on a colour field, into the schema - SLOT must be a constant that exists (a row of the program's tint-slot registry); reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:51:27 +03:00
34c2ac2cb9 attributes: @Clip(field) resolved as @Node is, and @OneOf takes constants as well as a prefix - one argument ending in _ is a prefix, otherwise each is a constant that must exist; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:34:33 +03:00
ebfbabdfd8 attributes: @Node(field), @Derived, @Text, @Multiline and @Key on a field, into the schema - @Node's field must be @Asset("gltf") or an @Ref to a registry whose record has exactly one, every failure reported; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:31:17 +03:00
b647964839 schema: every function a fn value can name, not only zero-argument ones - fn_type is the type a field sees with the states stripped, spelled as a field's type is (fn(A,B)->R), with params beside states; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:15:04 +03:00
42deb76c28 schema: ludicc --emit-schema / ludic schema, --check --diagnostics=json, and editor attributes
--emit-schema FILE writes the compiler's resolved view once the program type-checks: every
record (fields, types, defaults as written, docs, places, attributes), every registry with its
entries in their final order after the open-registry merge (key, constant, index, file:line:col
of the entry and of each field value, and which file contributed which keys), every const, and
the zero-argument functions a fn value can name. Deterministic, schema_version 1; the runtime is
left out. `ludic schema [file] [-o FILE]` wraps it.

--check --diagnostics=json prints every error as one JSON array on stdout: the checker's and the
module rules' all, a parse or lowering error as the last. Tokens and nodes now carry a column.

Fields take several @attributes; @Ref(Registry), @OneOf(PREFIX_), @Range(lo, hi), @Unit("..."),
@Asset("..."), @Color on a field and @AppendOnly / @ByKey on a registry change nothing but go into
the schema, and @Ref naming no registry is an error (every one reported). Fixtures:
examples/lang/attributes.ludic, examples/rejected/ref_unknown.ludic, cases in ludic-dev test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 18:06:54 +03:00
967f15ded9 render3d: baked textures before the PNG - a png_decode takes assets/baked/png, a cut-out load assets/baked/cutouts
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>
2026-09-29 17:30:57 +03:00
7444638030 inflate: its context in a caller-owned record, allocation-free per block; the state-backed calls kept
ZInflate (z_inflate_new) holds the bit reader, the RFC tables and every table and scratch list a block
builds, rebuilt in place: an inflate allocates nothing, and a worker thread inflates in a context of its
own (made on the program's thread). z_inflate_in / z_uncompress_in / z_gunzip_in take it; z_inflate /
z_uncompress / z_gunzip and every caller (image, atlas, tiled, render3d's png_decode) are unchanged and
work in RtInflateState's one context. The old per-call lit/dist tables were also leaked on an error
return; there are none now. Compiles: Maroon Lake headless, examples/library/tiled_p2 and tiled_p6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:25:16 +03:00
7c74d95efa render3d: cut-out padding on every core - tex_dilate's rows per pass through Job.parallel_for, bytes unchanged
Within a pass a row writes only its own still-masked texels and reads only neighbours already let go,
which no row writes that pass, so the result is the single-threaded one. The worker takes a DilateJob
of plain buffers and allocates nothing. tex_dilate_bytes (safe_api) and examples/rendering/dilate.ludic,
which checks it against the old loop on RGB and RGBA atlases of sizes that do not divide (DILATE OK).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:11:21 +03:00