Dungeon Critters
Dungeon Critters spawns up to 80 animated critters on a canvas dungeon floor, each independently driven by a single shared FSM definition. It demonstrates createBehavioralFsm — define behavior once, pass a client object per call, and the FSM tracks state for every client separately via an internal WeakMap.
What It Demonstrates
Section titled “What It Demonstrates”createBehavioralFsmfor shared behavior — one FSM instance drives all 35–80 critters. No per-critter FSM instantiation.- Per-client state via
WeakMap— the FSM stores nothing on the critter object itself. State is an internal implementation detail, not a property you can accidentally overwrite. - The client object IS the context — every handler receives the critter as
ctx. Handlers readctx.x,ctx.y, writectx.vx,ctx.vy, record timestamps. The client is both the key and the working surface. - Multiple clients in different states simultaneously —
fsm.handle(critterA, "tick")andfsm.handle(critterB, "tick")resolve independently.fsm.currentState(critterA)may return"chase"whilefsm.currentState(critterB)returns"idle".
States
Section titled “States”| State | Color | Behavior |
|---|---|---|
idle | dim grey | Stationary; occasional random fidget; drifts back toward home territory if it strayed |
patrol | green | Moves toward a random waypoint within its home territory circle |
alert | amber | Stops and faces the player; blinking ! indicator; auto-disengages after 2.5s |
chase | red | Pursues the cursor at full speed; critter inflates visually |
flee | near-white | Runs away from the click point for 2s then returns to idle |
Key Pattern
Section titled “Key Pattern”One FSM definition, created once, drives every critter. An input payload map declares what each input carries, so handlers receive typed payloads with no annotations or casts:
The game loop dispatches inputs across all critters each frame:
No timers in the FSM. With 50+ critters, independent setTimeout chains would be chaos. Instead, _onEnter stamps Date.now() on the client, and tick handlers compare elapsed time. One requestAnimationFrame loop drives everything.
FSM sets velocity; the game loop integrates position. Handlers write ctx.vx / ctx.vy (behavioral intent). The game loop applies x += vx and clamps to canvas bounds. FSM handlers stay focused on what the critter wants, not pixel math.
Interacting With the Demo
Section titled “Interacting With the Demo”- Move the cursor over critters to trigger detection and chase
- Click to blast critters within 80px — only chasing critters flee
- Click directly on a critter to select it and open the inspector panel
- Sensing range slider — adjust detection radius live for all critters
- Show sensing radius — toggle detection circle visualization
- Spawn — add 10 more critters (up to 80 max)