Commit graph

653 commits

Author SHA1 Message Date
9660587e10 compiler: free a string an expression made once it has been used
The left half of a + chain, a template's pieces and holes, a number's text and a side made only to be
compared are marked fresh and freed after the +, ==, != or print that reads them. lp_int_str and
lp_long_str move their digits to the start of the buffer, so the pointer they return is the one
malloc gave. Reseeded. examples/lang/string_temps.ludic: kept intermediates stay good, and 20,000
rounds grow the heap 0 bytes (2.9 MB before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:06:45 +03:00
2fe4018d28 Merge branch 'r3d/zero-leak' into lang/uifree 2026-09-28 11:40:43 +03:00
3d7b12c98c changes: gltf_cached
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:39:49 +03:00
de85f201ad render3d: gltf_cached - a still model loaded once by dir, file and node
A game placing props while it plays (a sign an animal leaves, a fish, a bobber) loaded each through
gltf_load, which reads the file and parses its document every time. gltf_cached finds the same
(dir, file, node) by comparing the names in place and hands back the model the first load made;
a model whose document is read after the load (ludic.anim's clips) still goes through gltf_load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:39:41 +03:00
a465984c52 fix(render3d, runtime): a glTF document is freed whole, layouts found by number, block records reused
- Json.free_all (value_free_all): a parsed tree's nodes, lists and strings. render3d frees each
  glTF document that way at the next load; the names kept out of it are copies (a primitive's
  material, a skin's joints, an animation clip's name in ludic.anim) - a model's strings were
  ~640 KB left behind per load
- jp_number made a digits list per decimal in a document and never freed it
- gvk_layout_id matches a mesh's layout as numbers in a scratch made once, against the layouts
  known end to end; a new mesh no longer builds a key string
- gvk_mem_new puts a new block into the record of one given back rather than appending, so a
  buffer made again every few frames no longer grows the block lists

steady.ludic adds a glTF parsed and freed whole 200 times: 0 bytes (38,400 before the digits fix),
beside the buffer path and the frame, still 0. A model loaded and let go still keeps ~2 KB a round
(texture and buffer handles are not reused yet); it is bounded at 8 KB a round.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:36:42 +03:00
0068411946 ludic.ui: a float in a text's hole written to one decimal from kept texts (its whole part from mm_int, its tenth a literal) - a bar's width moving every frame made a new string every frame, and read 7.470598e-07%
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:29:11 +03:00
8fbd7c7e60 Merge branch 'lang/ecs' into lang/uifree 2026-09-28 11:22:25 +03:00
4c4d225c03 Merge branch 'lang/chunks' into lang/uifree 2026-09-28 11:21:22 +03:00
0ec39f8e4e Merge branch 'r3d/zero-leak' into lang/uifree 2026-09-28 11:21:22 +03:00
866037a0d7 fix(render3d): nothing allocated in the steady state - the Vulkan allocator reuses its records
gvk_mem_new made a one-slot []pointer per allocation, and turned the requirement's size and
alignment into strings to read them as ints; gvk_list_drop_last rebuilt the spare-record list to
drop its last entry; gvk_mem_id did the string round trip on every free. One slot is kept
(gvk_map_slot), int() truncates a long, the spare list pops. Every Text.to_int(string(x)) in
render3d is int(x) now.

Vk.heap_bytes() (vk_mac.ll: malloc_zone_statistics' size_in_use; 0 on Windows) and
examples/rendering/steady.ludic, in the suite: a buffer released and made again 5000 times and
600 whole frames gain 0 bytes each - the allocator before this, 1,120,000 over the 5000.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:14:56 +03:00
50f255eea4 ludic.ui, runtime: the last per-frame allocations in the interface - the top popover found without a list, the popover draw list and the clip stack kept, a select's options in a list the node keeps (and matched to its value without writing the index), a number's text kept on its Value until it changes (value_num_set / Value.num_text), a key's label kept per key code until the layout changes it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:11:48 +03:00
e807fd556c ludic.nav (23.5b): a baked mesh resident by chunk
nav_load_index indexes a saved mesh's tiles (cell, offset, size, ref) from their headers and keeps the file open, with no tile in. nav_tiles_keep reads in the tiles within rin of any player and removes those past rout of all; the file is read through Ludic's pack-aware file_open. The file format is unchanged. tiles_test: tiles come and go with the point, a path works across the seams once they are in, and a reset closes all. Measured on the baked maps (the 900 / 1100 m ring round the start): Maroon 617 of 1807 tiles in, 8.9 MB instead of about 27 MB for a person and 5.4 MB of about 14 for a large walker; Lamar 391 of 1147. Both libraries are rebuilt; nav 17/17 on the Mac and the PC, DLL KERNEL32 only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:07:27 +03:00
a26cc2eea7 ludic.base: StrBuf and StrTable - a line written into a kept buffer and interned, one string per distinct text
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:04:07 +03:00
3915af34c2 Merge branch 'r3d/bc-target-fix' into lang/uifree 2026-09-28 10:43:44 +03:00
706c7abc55 ludic.ui: no number made text to be read back - a style handler returns before any text for a key not its own, grow/alpha/animations/scroll read numbers as numbers, el_secs split once per text, a tooltip translated when its title changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:43:44 +03:00
1447304dd7 fix(render3d): nothing draws into a BC7 texture - sampler binding binds, and compressed images get no mips
Metal aborted a net.sh guest ("MTLPixelFormatBC7_RGBAUnorm is not color renderable"): the crash
report's main thread was in vkQueueSubmit from gvk_once_end, MoltenVK encoding a vkCmdBlitImage
through a render pipeline. water.ludic bound its reflection by sampler name and then asked the
BOUND texture for mips - on Vulkan the bound texture was not the reflection but the last one
bound, since 23.3 a BC7 kit texture. gpu_bind_sampler now makes its texture the bound one, as
OpenGL did (and water binds it explicitly); gvk_tex_mips and gvk_mips_now skip compressed images;
gvk_pass_begin leaves out a compressed colour attachment and says so.

tests/net.sh passes (main e515bd87 built against it; host and guest agree on 196 animals, the
people, the board and the lost hiker).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:34:23 +03:00
5f2900ffda Merge branch 'lang/trail' into lang/uifree 2026-09-28 10:22:36 +03:00
0be4230487 Merge branch 'r3d/height-format' into lang/uifree 2026-09-28 10:12:48 +03:00
f294484c02 Merge branch 'lang/ecs' into lang/uifree 2026-09-28 10:12:48 +03:00
1675eb451f perf(render3d): the terrain's height and normal in R32F + RG16F, 128 MB where one RGBA32F was 256
The first generation pass's R32F height is kept as ter_height_tex (not copied into an RGBA32F),
so every reader of the height - placement, the read-back, physics, selftest16's 9 mm - sees the
same 32 bits. ternormal.frag writes the baked normal's x and z into ter_normal_tex (RG16F), and
the six places that read it (terrain.frag twice, tersun.frag, grass.vert, grass.mesh,
grass_cull.comp, which takes a third texture at binding 6) rebuild y. SPIR-V regenerated.

Maroon Lake's play, headless Vulkan: 2793 -> 2647 MB. The camp's frame: 0.066% of pixels differ by
more than 8 (mean 0.024/255), isolated grass blades at the slope gate. ludic-dev test 307/307.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 02:31:18 +03:00
28a529088f changes: a dispatched action's record kept by the queue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 02:09:35 +03:00
292672a019 build: reseeded on f104995 with a dispatch's record kept by the queue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 02:09:21 +03:00
7b864a3201 Merge branch 'lang/foundations' into lang/nav 2026-09-28 02:03:09 +03:00
fffedf71d0 ludic.physics, ludic.character, ludic.vehicles: fact records reused, and phys_sink allocates nothing
The per-frame sweep over physics, character, vehicles and nav found three fact makers that made a record per fact: contacts and splashes, the character's and the vehicles'. Each keeps a pool now, as wildlife's and npc's do (q_unheld). phys_sink, asked on every removal, built two new lists each time; it filters into a kept spare pair and swaps. ludic.nav's paths and crowds already allocate nothing, and none of the four builds a string per call. What remains at load, reset or a world swap is not per frame. vehicle_feed takes its state mut. vehicles_test: 1000 fed facts, drained turn by turn, use at most 4 records. All packages 421.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 02:03:09 +03:00
e8a34255ac Merge commit 'f104995' into lang/ecs 2026-09-28 02:02:11 +03:00
93bc8a90db compiler(0.R): a dispatched action's record is the queue's to keep - one list per action, filled in place, all free again when the drain ends
dispatch lowered to ludic_act_push(k, new A { ... }): a fresh record every dispatch, and an input
system dispatches Move and FrameTime every frame. The queue now keeps a list per action
(kept<k>, used<k>); ludic_act_new__A hands out the next (made only when all are queued), new's
emitter fills it field by field as it fills a fresh one (every field, default or given), and
drain_actions sets every used<k> back to 0 once the queue is empty. A reducer only ever reads
its action during the drain, so nothing sees a record after it is reused. Actions are visible
to the program's file, where the queue lives, as reducers already were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 02:02:11 +03:00
fe2d2bacd7 ludic.base, ludic.wildlife, ludic.npc: fact records reused, not made per fact
wl_fact made a new WildFact per fact, about one a frame, and Ludic never gives one back. ludic.base's q_unheld(q, pool, out) lists the records a queue no longer holds: neither waiting nor handed out by its last drain, which is good until the next drain. ludic.wildlife and ludic.npc now keep a pool, hand out a free record, and make a new one only when every record is held. Taking a record writes the state, so wl_fact / np_fact and the few callers holding their state read-only take it mut. Tests: q_unheld's rules (an empty drain holds on, the safe side); 3000 prints drained frame by frame use at most 4 records; 500 arrivals drained turn by turn use at most 4. base 37, wildlife 27, npc 17; all packages 420.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 01:58:19 +03:00
f1049953c4 Merge branch 'lang/foundations' into lang/uifree 2026-09-28 01:51:56 +03:00
4d8526ad7e ludic.ui, runtime, compiler: a component call's answer is written into a pooled record while the screen is built (call(p, name, args, into); value_into_*), a component root's passes pooled, an icon's atlas and name read in place; reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 01:51:56 +03:00
3e3c6088fb Merge branch 'r3d/bc-textures' into lang/foundations 2026-09-28 01:47:19 +03:00
862b048b17 Merge branch 'lang/foundations' into lang/nav 2026-09-28 01:38:54 +03:00
1a1110526e Merge branch 'lang/ecs' into lang/uifree 2026-09-28 01:29:02 +03:00
ea5eedb5a5 ludic.ui: a hole's text found by its pieces in the memo (built only when it first reads that way), avals left for the attributes read as text, a class's [root] made once, and a dev build's reload lets go of every file it read only to compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 01:29:02 +03:00
8cc4bdf66b feat(render3d): 23.3 - a .dds beside a .png is uploaded BC-compressed with its whole mip chain
The device's textureCompressionBC is asked for and remembered (gvk_has_bc). tex_load_ex prefers a
DX10 .dds with the full chain beside the .png (not for an edge-padded cut-out atlas):
texture_dds.ludic reads BC7 / BC5 / BC4, gpu_tex_compressed makes the image with every level and
no colour-attachment use (a compressed image is only sampled and copied into), and
gvk_tex_upload_blocks copies each level's blocks from one staging buffer. A colour map is BC7
sampled as sRGB, a data map BC7 read as it is. examples/rendering/bc.ludic holds it, in the suite;
ludic.lab's plate carries its .dds (its three shots render at 56-60 dB against the .png's).

ludic-dev test 307/307, no Vulkan SDK in the environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 01:23:45 +03:00
7a637967fe fix(leaks): effects_live_into / effects_live_count and hints_rail_into - a HUD's read-outs into lists it keeps
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 01:21:53 +03:00
c1dc475438 Merge branch 'lang/foundations' into lang/nav 2026-09-28 01:20:14 +03:00
6c663cc87b Merge branch 'r3d/moltenvk' into lang/foundations 2026-09-28 01:17:39 +03:00
9425bdc4e5 feat(render3d): 22.10 - ludic.render3d carries MoltenVK, so a Mac program has its Vulkan driver
native/build.sh builds MoltenVK v1.4.2 from its pinned, checksummed tag (its dependencies at the
commits its ExternalRevisions pins), thinned to arm64, id @rpath/libMoltenVK.dylib, signed ad hoc;
its licence goes in native/LICENSE-MoltenVK. Every render3d program links it through its rpath -
the package's lib/ while developing, Contents/Frameworks in a bundle, where ludic bundle puts and
signs it - and vk_mac.ll also looks for @rpath/libMoltenVK.dylib (after an SDK loader, so the
validation layer still stacks in development). The suite's SDK stand-in (vk_env) is gone: the
render checks draw on the MoltenVK the package carries. gpu_is_gl() removed; nothing calls it.

ludic-dev test 306/306 with no Vulkan SDK in the environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 01:16:26 +03:00
d7b449869e Merge branch 'lang/ecs' into lang/uifree 2026-09-28 01:05:35 +03:00
fa119d234b ludic.ui, runtime, compiler: an expression's Value comes from the screen's pool while it is built (never a state's start or an action's), true and false shared, calc() terms pooled and read in place, a model's list fields filled in place (value_set_ints/strs/floats/bools); reseeded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 01:05:35 +03:00
b841efe4ba docs(compass): what a capture hands back lasts until the next one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:58:53 +03:00
7b0deda1a6 fix(leaks): the compass's capture reuses its list and a pool of marks; what is caught is good until the next capture
The guide captured the story's marks every frame into a new list of new marks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:58:45 +03:00
bc66326385 Merge branch 'lang/foundations' into lang/nav 2026-09-28 00:54:13 +03:00
074189b73e ludic.nav (18.5): a walker the crowd avoids and never moves
nav_crowd_add_fixed adds a walker with no steering, and nav_crowd_place puts it (snapped to the mesh) where the player is, with the velocity they have, each frame. The others plan round it by that velocity, and whatever it was pushed by is undone at the next placement. crowd_test: six walkers aimed through a standing player come no closer than 1.71 m and all get past, and the player stays where it was put. nav 16/16 on the Mac and the PC; DLL KERNEL32 only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:47:47 +03:00
d83eabfdf1 ludic.nav, ludic.wildlife (18.4): a walker's separation, and a species' spacing
nav_crowd_add takes how hard a walker keeps its distance, and WildSpecies.spacing (default 2) is a species' room from others walking by, which the valley hands the crowd. Both libraries are rebuilt; nav 15/15 on the Mac and the PC, DLL KERNEL32 only. wildlife 26/26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:45:14 +03:00
25f4d49e43 build: reseeded on c2a5df4 with the Vk-links-the-window-layer rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:36:36 +03:00
95f2087148 ludic.wildlife (18.3): wildlife_listed - still one of this state's animals, so a crowd lets go of one a load or a clear removed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:36:31 +03:00
9ec1b3a9bf ludic.wildlife, ludic.npc (18.3): a crowd walker carries its walker's id (set_crowd(a, id), -1 off)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:32:25 +03:00
6ee496ab1c fix(render3d): three leaks in Vulkan play - sampler keys, mip blits, a program per actor
- gvk_sampler built a key string on every call it was reached (a texture read two ways in turn
  misses its per-texture cache each time); samplers are found by their seven numbers instead
- gvk_tex_mips_into allocated a VkImageBlit per level, every frame (the exposure measure's
  chain); it comes from the scratch ring, as does gvk_tex_grow_mips's copy
- gvk_program_new made a new program - modules, pipelines - on every call, and every actor asks
  for one; a variant is now made once and shared (a program is immutable), and one that cannot
  be made stays 0 for every later asker

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:30:11 +03:00
9a4137e9da wip(render3d): plan 22.14 - the OpenGL backend removed (suite 306/306, not yet handed over)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:30:11 +03:00