Reintroduce apps/arcade and the ten game crates that were ripped out of
the fork (assets, audio, blocks, coedit, gen, net, pkg, render, script,
session), from makepad-internal/dev. Reconcile against the fork's layout:
- makepad-game-sim/math come from the fork's existing libs/sim + sim/math;
the copied internal libs/game/{sim,math} are dropped and the kept game
crates plus arcade point at ../../sim and ../../sim/math.
- arcade AI is rewritten off the removed makepad-ai agent API onto the
makepad-ai-hub headless ChatProvider worker (example/cad pattern):
AiWorker{send,cancel,poll}, ai_worker_loop driving
ClaudeApiChatProvider(ClaudeCli), AiWorkerEvent availability/delta/
done/error mapped into the chat feed and authoring land_edit, with
main.rs send_message/cancel_request/event-drain rebuilt around it.
- the game-script sandbox is adapted to the fork's jailed splash storage:
every isolate gets mod.fs = splash_storage jail; splaes splash_storage
set_root_for_heap is now public so the ScriptHost (or Splash) can grant
a per-game jail root. ENT font: build.rs/dispatch.rs entities fill new
fork sim Entity/Part fields via ..Default::default(); world_raycast now
borrows mutably and returns the material id.
cargo check -p makepad-arcade and the game + arcade test suites pass.
Point the workspace at libs/asset and libs/render. Drop Arcade
frontend, gamemaker, rig, pack registry, physics, and the old
libs/game gameplay crates. Those live in the private sandbox repo.
blocks/npc.rs + sim/sense.rs. Utility scoring re-run every ~0.45s per NPC,
staggered so a crowd doesn't re-plan on one tick. Candidates scored additively:
visit a POI (tag appeal x distance falloff x novelty x jitter), loiter near
someone (gated on sociability), go home (grows with time away), wander (the
fallback, so a world with no POIs still moves).
Three things do the legibility work. POIs, so NPCs walk to THINGS rather than
coordinates. Per-NPC seeded personality (haste/patience/sociability/curiosity/
homebody) so identical config still yields unlike villagers. And a day clock
with a per-NPC phase offset — benches read as afternoon, doors as evening —
which is what stops ten villagers doing the same thing in unison. Activities
are deliberately only four (Idle/Travel/Dwell/Follow); routines come from
sequencing them, not from twenty verbs.
Sensing reads THE SAME SOLID FILTER the mover sweep uses, so perception and
collision cannot disagree. obstacle_ahead sweeps the NPC's own box rather than
casting a ray, because a ray through a doorway reports "clear" for a body twice
its width. Blocked -> jump if the top is in reach with landing room, else
sidestep toward the side with clearance (blended with the goal so it curves
rather than turning 90 degrees), else a stuck timer abandons the goal.
Reading the existing tests caught a bug in the new logic: "low obstacle -> walk
over it" is wrong, because the 0.55 step-up is a TERRAIN contract and
sweep_axis blocks against static boxes at any height. That branch is gone —
it was exactly the perception/physics disagreement this module exists to avoid.
Two bugs the tests caught:
- MUTUAL SOCIAL LOCK: two sociable NPCs each chose to loiter near the other,
permanently. One moved exactly 0.0 units in 90 seconds. A social cooldown
stops Follow being re-picked immediately
- VILLAGE DRIFT: an unbiased random walk has no centre, and a trace showed a
villager 43 units out with every POI inside 18. Wander steps past a 26-unit
leash now aim home
Cost against a 16.6 ms budget: 50 NPCs 0.007 ms/tick, 200 NPCs 0.046 ms/tick
(full sim step — a pre_step-only figure would be a lie, since without
step_world the NPCs never move and re-decide more often).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>