Commit graph

698 commits

Author SHA1 Message Date
ae5df73e72 ludic.jobs jobs_posted_count, ludic.steps steps_my_shares_into - a count and a kept list for what a party asks every tick
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:12:24 +03:00
fc0f3d3790 Merge branch 'r3d/prime-fix2' into lang/leaks2 2026-09-28 14:24:42 +03:00
c736fd3b09 render3d: buffer primes in a command buffer of their own, submitted at once
A co-op guest loading its models segfaulted in copyBufferToBuffer under vkQueueSubmit (0x68, AGX
LegacyBlitContext): a buffer primed into the frame's command buffer was released later in that
frame, and gvk_buf_release destroys a buffer no draw has marked, so the copy read a freed one. The
copies now run in their own command buffer before the frame's begins, checked against the slot's
handle as they are recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:20:00 +03:00
e8dc823009 Merge branch 'lang/play-leaks-own' into lang/leaks2 2026-09-28 14:09:13 +03:00
62a703974d test(ludic.nav): an index loaded again twenty times, its tiles in each time, holds the heap (0 bytes), the open files and the tiles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:09:00 +03:00
33dc9b6a18 Merge branch 'lang/play-leaks-own' into lang/leaks2 2026-09-28 14:02:58 +03:00
364888bcc9 fix(ludic.physics): a hull owns its box, so a boat called and sent away takes its shape with it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:59:25 +03:00
5cd53d4024 feat(ludic.physics): phys_own - a body owns a shape made for it alone, and phys_remove frees it with the body
A chunk's trunks and boulders put in and taken out made a new shape each time and freed none. The
table is by the body's index, made once at the world's size. tests/reuse_test: 1000 owned bodies put
and removed hold no more shapes and 32 bytes of heap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:59:25 +03:00
254097657e runtime: Log, DateTime.format, Input.text, Path, Mime, Fs, Os and Text keep nothing per call
Found by reading every builtin (Os.platform's 8 KB per call started it). Log builds its line only at
or above the threshold and frees it; DateTime.format folds through + so its pieces go; Input.text
encodes into one buffer; Path/Mime/Fs/Os free their temporaries on every path; string results of
Text/Path/Mime/DateTime/Os dirs are fresh and Text frees a fresh argument. Reseeded.
runtime_temps.ludic: 19.8 MB -> 0 over 20,000 rounds, 64 KB -> 0 over 200 of file work; clean under
MallocScribble. string_temps still 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:55:49 +03:00
404ee2ee3b Merge branch 'lang/ecs' into lang/leaks2 2026-09-28 13:46:18 +03:00
f8fe157352 Merge branch 'r3d/no-cmd-pool' into lang/leaks2 2026-09-28 13:41:23 +03:00
61a6f3c827 ludic.base: TextRing - a fixed ring of text buffers written in place, for a line made per event
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:35:34 +03:00
0141f99dd1 Os.platform/arch uname once; render3d primes a new buffer's Metal buffer
lp_os_platform and lp_os_arch malloc'd 8 KB per call (the uname buffer) and kept none of it:
string_temps now asks both every round, 327 MB over 20,000 before, 0 after. Reseeded. render3d:
MoltenVK made a mapped buffer's MTLBuffer at its first bind (fn_gvk_draw +4 blocks in the boat
window); gvk_buf_reserve queues it and the next frame's command buffer copies 4 bytes out of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:34:45 +03:00
67474a5cfe Merge branch 'lang/play-leaks' into lang/leaks2 2026-09-28 13:33:48 +03:00
7e9fa4473c render3d: a stream's cache is made with the stream - records and an arena - and evicts in place
stream_update made a Chunk and a words copy per new chunk (fn_stream_update +39 blocks / 13.2 KB a
window with the hiker in the drifting boat). The pool holds STREAM_MAX_CHUNKS records; the arena
is twice the layer's cap; eviction compacts it; a chunk that cannot be kept is gathered from the
scratch. stream_clear_all frees records, lists and streams. steady: 300 new cells, cap 256: 156 KB
before, 0 / -4.9 KB after, and every kept chunk's data checked against its cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:26:59 +03:00
ca69a3f9ae ludic.net: a message's record, bytes and lists kept - queued ones back to a pool once sent or taken, the inbox's two drains on, a read text interned
np_queue made a NetMsg and a buffer per message, every flush a new keep list, and every message
that came in a NetMessage and a buffer; nr_text a new string per text read. A party plays at
dozens a second, so all of it is kept now: a pool of MTU-sized records for the queue (a peer's two
lists swapped by the flush), the inbox's records in two halves swapped when a message finds the
inbox empty, net_written's one record, a relay hello's and a STUN request's bytes in one scratch
buffer, and the texts read out interned. Tests: 6000 messages with the heap flat (Os.heap_bytes),
and an inbox record that holds still across a drain and comes back two drains on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:25:46 +03:00
317d63d822 fix(ludic.jobs): fact records pooled (the ludic.wildlife idiom) - a record per deed that moved a job was never given back
tests: 200 jobs taken and given up make 2 records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:17:55 +03:00
36358e7a43 fix(ludic.fishing): fact records pooled (the ludic.wildlife idiom) - a record made per cast, bite and crossing was never given back
tests: fifty fish, their facts drained each time, make 5 records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:17:12 +03:00
968030535e fix(ludic.minimap): minimap_pins_near refills a list the state keeps - the compass asked every frame and got a new list each time
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:16:05 +03:00
6e132d25c8 fix(ludic.physics): a removed walker's place is taken again, and phys_close keeps the lists it empties - nothing grows per horse called or per world swap
tests/reuse_test: 200 walkers made and removed hold one place and 0 bytes of heap (Os.heap_bytes);
twenty closes and opens keep the same shape and walker lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:05:28 +03:00
b4774a46ee render3d: turn MoltenVK's command pooling off; steady ramps the draw count
The pools kept every command object ever recorded, so the heap grew each time a frame drew more than
any before (fn_gvk_draw under vkCmdBindVertexBuffers/BindIndexBuffer/Draw in the leakcheck; ~650 B a
draw). Off: 3.7 ms a frame either way over 520 actors. steady: 20 to 200 actors grows 5-7 KB with it
off and 120 KB with it on (bound 16 KB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:57:08 +03:00
e6729b3123 Merge branch 'lang/ozz' into lang/foundations 2026-09-28 12:53:14 +03:00
d6b3242b25 ludic.base: an IntIndex slot's list made with room for 16 rows, so filing in play does not grow it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:45:40 +03:00
8a4f3f5ad6 ludic.things: a fact's record kept, in two halves swapped when a fact finds the queue empty
th_fact made a ThingFact per placement, removal and use, and the valley places and removes Things
all day. The records come back two drains after they were handed out, so a reader placing a Thing
while it reads the last drain's list never sees one change (the test holds exactly that).
thing_use takes ThingsState mut, since it pushes a fact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:45:40 +03:00
9a56ee9b9d feat(ludic.anim): ozz-animation 0.17.0 underneath - a skin's skeleton and each clip built at load, sampled to 1e-4 of anim_mix (phase 19.1)
No bake and no new file: the skeleton comes from the skin's parents and rest pose (its own joints
and their ancestors - a kit holds several rigs), a clip from anim_read_doc's channels. Built by
native/build.sh from the pinned release; the Windows DLL imports KERNEL32 alone and passes there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:44:49 +03:00
22d1e2d66a Os.heap_bytes: the heap without the renderer; string_temps reads it
Vk.heap_bytes pulled the Vk module - and on lang/uifree the GL window path - into a plain program,
which then failed to link (_cgl_offscreen, lgl_GetError). Os.heap_bytes is malloc_zone_statistics
through a weak reference (0 where there is none, and on Windows). Reseeded. Docs for it and for
Json.free / Json.free_all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:31:42 +03:00
1adce5b58b render3d: free a texture's create infos and the .dds path it looked for; steady's model at 0
gvk_tex_storage freed none of ici, out, req and vci (8 blocks a two-texture model); tex_load_ex kept
dds_path_of's string. Found with malloc_history over 400 load/release rounds (712 bytes a round, all
of it these). steady: the model's bound is 4 KB over 200 (was 1.6 MB), and the frame is the least of
three settled windows - a valley self-test beside it read 87 KB once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:25:02 +03:00
328dee77c8 compiler: a string slice is a fresh temporary too
s[a .. b] is always a copy, so it is freed once a +, a comparison or print has read it. Reseeded.
string_temps.ludic adds a slice compared and a slice concatenated each round (960 KB over 20,000
before, 0 after) and a kept slice read after its +.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:25:02 +03:00
174a32c285 Merge branch 'lang/ecs' into lang/uifree 2026-09-28 12:19:16 +03:00
47e32eacd1 Merge branch 'r3d/recycle' into lang/uifree 2026-09-28 12:19:16 +03:00
d9c574f107 Merge branch 'lang/str-temps' into lang/uifree 2026-09-28 12:19:16 +03:00
93e6954fb1 ludic.things: things_spare_warm - spare records made up front, so play takes them from the first placement
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:09:52 +03:00
32050879a1 Merge branch 'lang/uifree' into lang/ecs 2026-09-28 12:09:33 +03:00
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
5ec13a073f steady: read the heap once the device is idle and two reads agree
Under the suite's parallel load a frame read ~80 KB high: MoltenVK's completion handlers release a
finished command buffer on their own thread and lagged. Settled, 12 of 12 runs four at a time pass;
the frame's bound drops from 64 KB to 4 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:04:09 +03:00
db3fa68052 Merge branch 'r3d/recycle' into lang/uifree 2026-09-28 12:01:33 +03:00
423ea12856 Merge branch 'r3d/recycle' into lang/ecs 2026-09-28 12:01:12 +03:00
9b482d609a render3d: recycle texture and buffer ids, free a released model whole, actor_release
A freed texture or buffer id goes on a spare list the next one takes; tex_note_size keeps sizes by id.
model_release frees prims, meshes, material names, the skin and leaves gltf_cached. actor_release
takes an actor off the stage and actor_new reuses its record (ECS's Things come and go all day).
steady.ludic: an actor round at 0 bytes over 2000.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:00:53 +03:00
be06477907 Merge branch 'lang/ecs' into lang/uifree 2026-09-28 11:57:31 +03:00
08a6fc8a3f Merge branch 'lang/chunks' into lang/uifree - the ground's heights by chunk and its Jolt ground per chunk; both new render checks kept (steady, chunks)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:55:55 +03:00
65b51c251e ludic.things: thing_slot - a placed Thing's reusable slot in the table
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:54:21 +03:00
1af62d0879 ludic.things: a removed Thing's record placed again, oldest first past a lag of 32; things_reserve sizes the grid once
thing_put and thing_spawn made a new record for every Thing placed, and the world places and removes
them all day (an animal's sign and bed, a drop, a fish), so play grew by a record per placement.
Removed records wait in a queue compacted in place; once 32 wait, the oldest is set back as new
with a new uid. ludic.base's grid_reserve makes the buckets for n rows now, since a rehash in play
leaves the old bucket array behind. Tests: a record comes back only past the lag, as new, with a
new uid, the indexes agree, and the spare queue stays bounded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:52:23 +03:00
5d71892b7c ludic.ui: every bar percentage (0% .. 100% to a tenth) made once when the memo starts, so a bar moving never makes its text in play
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:46:47 +03:00
0c3c0c5e6a feat(ludic.physics): phys_shape_free - a shape let go hands its id to the next, so a ground by chunk grows nothing (23.5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:43:40 +03:00
2fe4018d28 Merge branch 'r3d/zero-leak' into lang/uifree 2026-09-28 11:40:43 +03:00
96423634ad Merge branch 'r3d/chunk-heights' into lang/chunks 2026-09-28 11:39:50 +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