zones_set / zones_clear / zones_merge, zones_contains / zones_nearest and the clusters' bounding circles,
over fixed arrays of ZONES_MAX (16). WildlifeWorld.active freezes an animal outside (no state change, no
dice), NpcWorld.active a walker (and no birth outside), VehicleWorld.active a moored boat, and
ThingsWorld.restocks keeps a morning's restock inside; every default is "everywhere", so an unbound game
is unchanged. Tests: the merge (near players one cluster, far two, a player leaving splits a chain),
contains, nearest, the radius; wildlife, npc, vehicles and things pass as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ludic deps --allocs (25.2) found npc_pick_name making a list of the body's names at every spawn and
np_left making a new list of the recent names at every retire.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fence found np_fact_record making a record in five frames of every scenario: the pool grew to
each new peak of facts held at once, and q_unheld's free list and the queue's two lists grew with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
npc_set_crowd hands a person to a crowd. Its walk no longer steps or asks the way: its want is goal_x / goal_z / goal_speed (0 when it is not walking). npc_moved hands back the crowd's place: position, ground, heading from velocity, gait. Arrival, the act and the give-up after twenty seconds without progress are unchanged. Nothing allocates. npc 16/16.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A step round what is in the way, a target planned again after twenty seconds without progress, and a target given up are each pushed as NPC_F_STUCK (how, x, z), so the game can see where the way failed a person. The lake test expects one when a walker turns back from the water; the way test expects none when the world gives the way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts with 0.R5's state defaults in ludic.wildlife resolved; wl_step writes the push's answer, so
its callers take WildlifeState mut again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ludic.wildlife: WildlifeWorld.push / pushed_x / pushed_z (defaults change nothing); a ground
walker's step is slid clear of a still thing with a body from its species' size and scale, and
a step that gets nowhere turns away as the water does. Birds are not pushed; no dice added.
- ludic.npc: NpcWorld.push / pushed_x / pushed_z (a person 0.35 m round) in npc_walk, and
NpcWorld.clear (default true), which npc_line_ok asks at 1 m so a detour does not cut a trunk.
- ludic.aim: AimView.clear replaces AimView.ground and the ground march; the pick asks the line
a body's width short of where the ray enters the thing (aim_clear_at(t, back)), so a solid
thing does not hide itself. probe.ludic (the ground-agreement probe) and AimView.dry are gone.
- ludic.photo: PhotoLens.clear replaces PhotoLens.ground, PhotoLens.trunk and the march.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ludic.base's Queue<T> carries a QueueTag (its name and pending count): queue_new(name), q_push(q, v),
q_drain(q), q_clear(q) take no BaseState, and core_undrained(tags) names the given queues still holding
facts. A reducer on a package's state can now call that package's verbs (wallet_earn(wallet_st, n)).
ludic migrate state --prune (ludicc --migrate-prune) takes out each state parameter a function no
longer uses, nor anything it calls, and the argument that fills it - including an argument for a
parameter the callee has dropped, which is taken out before the call is checked, so a generic's T is
told by the argument that says it. A reducer keeps its state. --dry-run now counts the edits it would
make. Every package was moved with it: 608 base_st parameters and their arguments, 1889 edits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ludic migrate state packages <every example program> packages/ludic.lab/example/plate.ludic
1804 vars into 126 states, 64 into lets; 23498 edits in 460 files
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>