Commit graph

1177 commits

Author SHA1 Message Date
eeb7a3d5fb Merge lang/png-jobs into main (gathering every branch; main's side kept on conflicts)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:21:21 +03:00
9883008cd1 Merge lang/memory-fence into main (gathering every branch; main's side kept on conflicts)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:21:20 +03:00
e72a4d0a13 Merge grass/far into main (gathering every branch; main's side kept on conflicts)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:21:20 +03:00
737d6e1859 wip: uncommitted work gathered before the branch is merged into main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:18:48 +03:00
8a997fd7ce ludic.wildlife pins: a tie between thirst and hunger sets off for water (GO_DRINK), as the package does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:15:54 +03:00
754faa7730 Merge pkg/wildlife-pins 67d477af: phase 27.2a - every ludic.wildlife transition and its dice pinned before the move onto @Machine
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:15:14 +03:00
67d477af77 ludic.wildlife: pin every transition of the 15 WILD_* states and the dice each draws (phase 27.2a)
Sixteen test programs under tests/*_pins_test.ludic, on today's code, each one tick from a set
state through the public step (wildlife_tick_ground / _bird / wildlife_tick, wildlife_release,
wildlife_new): the state it goes to, its timer, and how many of the package's rolls it took and
which (the next roll is read off a newborn's yaw, compared with a stream of the same seed). IDLE,
WANDER, ALERT, WARY, FLEE, CHARGE, GO_DRINK, GO_FEED, DRINK, APPROACH, EAT, TRAPPED, FLY, LAND,
PERCH; the droppings' roll before them; the newborn's seven (eight for a bird); what the step
leaves alone. tests/pin/ holds the shared step, the dice reader and two species of its own (a tame
horse, a crow that never lands on its own). Compiled, not run here: 27.2 holds to them unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:57:15 +03:00
314b8ce194 Merge tools/deps-reach 2a18d9b5: ludic deps' reach sets packed 60 states in a 32-bit int, so states 32 apart shared a bit - 30 a word now; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:51:21 +03:00
2a18d9b51f ludic deps: the reach bitsets hold 30 states to a word, not 60 - an int is 32 bits, so states 32 apart shared a bit and a function could reach fewer states than it takes; examples/state/reach_wide.ludic and a deps case hold it; reseeded (bootstrap-cfree fixpoint)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:49:34 +03:00
d7adc28125 Merge lang/machines e4265f5f: phase 27.1 - a state machine as data (@Machine(Record.field) on a registry of transitions: generated row reducers and tick, the field written only by its table, graph checks, schema machines); reseeded, syntax regenerated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:39:26 +03:00
e4265f5fdb feat(lang): 27.1 - a state machine as data: @Machine(Record.field) on a registry of transitions
A registry marked `@Machine(Deer.mood)` is the transitions of a machine over that enum field of the
records a state's Table<Deer> holds. Its record has from and to (the enum's variants), on: string (an
action's name, "" for a transition the tick asks), guard: fn(Row<Deer>, reads...) -> bool and
enter: fn(Row<Deer>, reads...) -> void; the states are the enum's variants and the start is the
field's default. The rows are data (an .lres or defs), the names the studio already edits.

Written by the compiler (machines.ludic, machines_write.ludic): for each action an `on` names, a row
reducer in the registry's file (named ..__machine__DeerSteps, so it sits beside the program's own
row reducer on the same action, after it): the row's state, the first transition from it on that
action whose guard passes, the field set, enter run - guards and enters called by name. When a row
leaves a state on a guard alone, `state DeerStepsMachine` (the kept row view) and
deer_steps_tick(m: mut DeerStepsMachine, s: mut Herd, reads...), one transition a row a tick.
Nothing allocates.

The table is the whole machine: the field written anywhere else - an assignment, or a `machine`
block's become over it - is a type error (check_stmt.ludic, ck_machine_write). Guards and enters take
the row first, are the record's module's, keep a row reducer's rules (and may be handed the row);
a guard writes nothing through it. The graph is checked, each error at its row (in the .lres when
the rows are there): a state never reached from the start, a state with no way out, an `on` naming
no action or an action with no @Target, a self-transition with no guard, two ways out of a state on
one trigger behind an unguarded first. Also refused: @Machine off a registry, a field that is not a
plain enum with a default, a @Column field, no table (or two) of the record, a transitions record of
another shape, a machine outside its table's state's module.

ludic schema's code section gains `machines` (registry, record, field, enum, table, start, states,
actions, tick, module, at); ludic deps names a machine's reducer `reducer Deer in Herd.deer on Spook
(machine DeerSteps)`. vocab @Machine; docs annot-machine, kw-machine; LANGUAGE.md "A machine as
data"; examples actions/machine (+ deer_steps.lres) and ten rejects; test.ludic feat, reject and
schema cases (not run); changes/machines.md. Reseeded; bootstrap-cfree fixpoint holds (317642
lines); Maroon Lake's `ludic build --check` is clean against this tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:37:37 +03:00
a3e3ea2f8e ludic_syntax.h regenerated for the row reducers' attributes (@Target, @RowVerb, @Column) - ludic-dev syntax
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:30:00 +03:00
a637aec63e Merge lang/row-reducers ab0b84b8: phase 27.3 - reducers on a table's row (reducer T in S.table on A, @Target, Row<T>, @RowVerb, @Column), an allocation-free drain, and ludic schema's row_reducers and row_verbs; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:20:37 +03:00
ab0b84b87e feat(lang): 27.3 - a reducer on a table's row: reducer T in S.table on A, @Target, Row<T>, @RowVerb, @Column
An action names one row of a ludic.base Table<T> by its handle, in a field marked @Target, and
`reducer Deer in Herd.deer on Spook(r: mut Row<Deer>, n: Noise, a: Spook)` runs once, for that row
alone (the table may sit down a path, S.w.tab). The drain resolves the handle (tb_row) and hands the
reducer a Row<T> - new in ludic.base: tb, row, h, rec - that the queue keeps, one per row reducer,
filled in place, so a targeted action allocates nothing; a stale handle runs nothing, and
LUDIC_ACTIONS_LOG=1 prints a line for it (@alloc_ok). Row reducers order among an action's by their
state's name, then the table's path.

Checked at compile time (actions_rows.ludic): the row reaches r.rec and r.h only - r.tb / r.row
refused, the view never assigned, stored, copied or handed on except to a @RowVerb (a function of
the record's own module taking Row<T> first; any other function taking a row is refused); a field
marked @Column (a table column mirrors it) is not written through r.rec; only the module owning the
state declares a row reducer; one @Target, an int, per action; the states between the row and the
action are read. `mut` is allowed on a Row<T> parameter.

ludic schema's code section gains row_reducers (record, table, state, action, target, predicted,
net, module, at) and row_verbs (name, record, module, at), and every action its target; row
reducers are left out of `reducers`. ludic deps and ludic-lsp name a row reducer
`reducer Deer in Herd.deer on Spook`. vocab: @Target, @Column, @RowVerb; docs/language pages;
LANGUAGE.md "A reducer on a row"; examples actions/rows and ten rejects; test.ludic feat, reject and
schema cases (not run); changes/row-reducers.md. Reseeded; bootstrap-cfree fixpoint holds (307497
lines); Maroon Lake's `ludic build --check` is clean against this tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:14:59 +03:00
d22abae476 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:38:39 +03:00
9ce51c2d49 Merge r3d/golden-json 7918103b: the ground_fill golden is valid JSON, and its test fails a file that is not one whole object
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:36:59 +03:00
7918103b8b render3d: ground_fill_golden.json is valid JSON (the cover and solid blocks each closed one brace too many)
gg_cover_json and gg_solid_json ended in plain strings with "}}", which only a template literal reads as one
brace: the cover's closed the top-level object before "solid", and the solid's left a stray "}" at the end.
The committed golden is fixed by hand to what the generator now writes.

ground_fill_test also checks the golden as JSON: one value with nothing after it (Json.parse reads the
first value and ignores the rest, so it alone would not have caught this), an object, its eleven sections
in order. The generator notes what the studio asked: past the density patch an empty tile reads 0 for R and
G (only past the map's edge does a texel clamp), and several clearings multiply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:36:08 +03:00
176dc79b49 Merge r3d/painted-zones: ground_fill's candidate as a pure function, solid layers with stable ids, clearings as data, and a conformance golden the studio shares
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:25:43 +03:00
87b95625ff render3d: ground_fill's golden conformance data, generated by tests/gen/ground_fill_golden.ludic (two runs byte-identical)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:25:43 +03:00
530956889b render3d: a README note on painted ground layers, and the changeset
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:24:23 +03:00
54e267dfa6 render3d: ground_fill's conformance test and the generator of its golden
tests/ground_fill_test.ludic compares gg_json (gf_hash over fixed inputs, Math.lerp cases whose order
matters, the yaw as model.vert turns by it, a hand-written density, one cover chunk with a clearing and
one solid chunk) line for line with tests/ground_fill_golden.json, and checks ground_fill draws exactly
the candidates and a solid layer's far bands a subset of band 0. The golden is written by
tests/gen/ground_fill_golden.ludic, run from the repository root; it is not committed here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:24:23 +03:00
e535879262 render3d: ground_fill's candidate is a function of its own; solid layers with stable ids; the trample as clearing discs
ground_candidate answers (layer, chunk, band, cell) -> keep, x, z, scale, yaw, seed, wind from pure gf_*
steps and the density read between them; ground_fill draws its answers with the same operations in the
same order. A solid layer fills at step0 and band 0 whatever the camera, a far band drawing a stable
subset (draw 7 under (step0/step_b)^2), each thing an int id from (layer, chunk, cell), listed per chunk
into a caller's GroundSolids or answered by id. r3d_ground_clearing hands the trample over as discs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:23:53 +03:00
740ac2a879 Merge lang/i18n-data-fill 5d9f9cf2: ludic.audio has one aud_play holding the only Audio.play_at
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:31:17 +03:00
5d9f9cf284 ludic.audio: one call into the runtime's playback - aud_play holds the only Audio.play_at (the one float -> Q16.16 crossing), and aud_emit and aud_ui both go through it; aud_ui had its own play_at since the interface channel, which the game's guard (one play, one play_at) could not hold. README and the module's words follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:24:36 +03:00
a6ce456c60 Merge r3d/tiles-open-fix c18579f7: tiles_open_test asks for what a device would have made, not the frame counter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:24:13 +03:00
c18579f777 tiles_open_test: "nothing on a device" asks what terrain_from_baked would have made - the coarse height and normal textures and the page pool - not gvk_frame_no, whose default is 1 (env.ludic), not 0: the open path ticks no frame
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:23:36 +03:00
ad7cbd8e2e Merge r3d/tiles-open-file 42147937: terrain_tiles_open_file - a map's terrain bake opened for questions with no device
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:19:33 +03:00
69adbddb46 Merge lang/i18nhandlers 56762050: the key check walks @On listeners, hooks, tests, computed fields and scenes; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:19:33 +03:00
42147937b2 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:13:18 +03:00
56762050fb i18n: a key used only in a listener counts as used - the key check walked prog but not the bodies kept beside it (@On listeners, the lifecycle hooks, tests, computed fields, scenes), so their keys were never checked against en.po and ludic schema listed them unused (the game's 15 itemuse.* keys, emitted through ItemPlace); they are walked now as privates.ludic walks them. examples/lang/i18n_listen and its schema case (added, not run); reseeded, bootstrap-cfree fixpoint holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:12:25 +03:00
744a03bdfa Merge lang/i18n-data-fill 88823314: L is keys only - a plain string is drawn as it is in every language
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 03:11:16 +03:00
8882331421 ludic.i18n: the English path is out of L (26.9) - a plain string is drawn as it is in every language (a player named Settings stays Settings), a key, a key glued into text and a bracketed line are made as before. The pattern tables, the sentence and padding lookups, Ln, i18n_pattern_count and kr_words' English-argument lookup are gone; a language's .po is read for keys and plurals. Tests move to keys; README and a changeset say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:58:13 +03:00
e2aa928e95 Merge lang/i18nerror 25a57c09: English left is an error under a lang line; ludic.ui's own words are keys; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:49:43 +03:00
25a57c099b i18n 26.9, error mode: English left is an error under a lang line (a template's words, a text attribute's, a choice that reads as words, a @Text row's English), ludic deps still counting english_left; hole counts and undescribed splits stay warnings. ludic.ui's own words are keys - ui_tk(ui_st, k"ui.right_click", plain) for the key field's Right / Middle / Left click and press a key..., its plain text for a program with no translator; the examples' en.po / tr.po carry ui.*, i18n_ui checks clean, rejected/i18n_keys/english_left holds the error, data_missing counts 2; LANGUAGE.md and the changeset; on lang/foundations 2288feeb; reseeded, bootstrap-cfree fixpoint holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:45:15 +03:00
490dc0f563 Merge r3d/height-askers 130b9091: only the loaders page; every other terrain question reads the whole copy, a resident slot or the file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:43:39 +03:00
130b9091d1 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:43:17 +03:00
2288feeb59 Merge lang/i18n-data-fill 96798c0f: ludic.anim transitions go via a one-shot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:38:41 +03:00
96798c0fbb ludic.anim: a transition may name a via one-shot - a request from from (the state last asked, or "*") to to goes by it when the rig carries its clip, and the one-shot's next hands on. The game's hiker picked sit_down / stand_up / board_boat / leave_boat in code; the set says it now. A via edge is not an edge for a request's own fade. animset_test holds it, with a via the rig has and one it has not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:34:07 +03:00
9a1cae1d52 Merge r3d/height-file c2fc8f78: terrain_height_file - the exact height without touching the tile cache
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:30:26 +03:00
c2fc8f78f2 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:29:55 +03:00
12553edb26 Merge 7c4e6870
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:07:11 +03:00
ea451dfda2 Merge 986fba3586
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:07:11 +03:00
7c4e6870b3 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:03:43 +03:00
986fba3586 ludic.i18n: inside a bracket an argument's bytes ride raw - trf escapes a 27 or 31 only outside brackets, and the decoder unescapes only there, so a bracketed line carrying a key with holes (Ada's brief: its legend line's escaped arguments) keeps its own escapes whichever way it was put in (a game's txt_key raw, or trf). 81e1f7d8 unescaped inside the bracket and split the legend's arguments out of the brief. nest_test holds a key whose hole holds a key with two holes: bare, bracketed raw, bracketed through trf, and escaped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:00:20 +03:00
00b45742d1 Merge lang/i18n-data-fill 81e1f7d8: ludic.i18n's L makes bracketed lines (29/30) itself, outermost first
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:48:18 +03:00
81e1f7d8e6 ludic.i18n: a line bracketed inside another (bytes 29 .. 30) is made by L itself - each bracket outermost in, a key whole through keyed() so its escaped arguments stay whole, and a split on 31 never inside a bracket (a game's txt_key puts its nested line raw); a stray bracket draws nothing. The game's tx_L did this alone, so L in a test or anywhere else drew the brackets (26.9's move, made now). nest_test holds it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:39:44 +03:00
3bf07ce606 Merge lang/builtinnames d4df925b (attrs before export 26b986de + the builtin-name refusal); reseeded, fixpoint 300381
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:15:05 +03:00
d4df925b8f checker: a function named like a built-in a call always takes is refused at its declaration - function words(st, k) compiled and every call became words(n) with a pointer for n (invalid IR, far from the cause); the table is selfhost/check/check_builtins.ludic, emit_call's built-ins no find_fn guard lets a declared function take, and ludic-dev syntax --check holds it to emit_call both ways; every other built-in (buffer, floats, double, ...) yields to a declared function in the checker as it already did in codegen. string_temps' keep and cross_heap_test's keep renamed (hand, keep_cell); rejected/builtin_call_name and functions/builtin_yield with their cases (added, not run); reseeded, bootstrap-cfree fixpoint holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:10:24 +03:00
26b986de5b parser: an attribute before export is kept - @ToClients export event E was a local event (the attributes read in front of export were dropped when the declaration was parsed afresh one call down), as were @Sync / @Owned / @Server / @On / @Public ... before export; and export @ToClients event was refused. Attributes and export now read in either order into one declaration, and a wrapper marks what it added exported (g_at_keep goes); examples/networking/net_export and three schema cases (added, not run); reseeded, bootstrap-cfree fixpoint holds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:03:44 +03:00
64adb052b4 Merge commit 'd703d79feb' into lang/foundations 2026-09-30 00:56:43 +03:00