# SIMD-CHAT: "Synapse City", a game design document

*An interactive simulation of the IMD swarm on a neural-network map*

Version 1.0 (2026-10-16). Status: design for implementation. Language: English.

---

## 0. How to read this document

Each claim carries one of these labels:

| Label | Meaning |
|---|---|
| **[FACT]** | Checked against a cited primary source or a live API response I read while writing this (sources in §14). |
| **[INFERENCE]** | My conclusion from the facts. It is reasoned, but nobody has measured it for this project. |
| **[DESIGN]** | A design decision. It is a choice, not a claim about the world, and developers may change it. |
| **[UNCERTAIN]** | Something that may not hold or that I could not check. A validation step is given. |
| **[OPEN]** | A question only the requester can answer (collected in §13). |

Summary. The game can be built on the web today. Real agent IDs and live activity are publicly available from the IMD swarm API **[FACT]**. A 3D WebGL client (three.js) with instancing and level-of-detail (LOD) can show every enrolled agent at once **[INFERENCE]**. The core interaction (click → stop → face the camera → info panel → click elsewhere → resume) only needs a small per-agent state machine plus standard animation cross-fading **[INFERENCE]**.

---

## 1. Data: who "all IMD swarm agents" are

### 1.1 What the sources establish
- IMD (identity.md) has **2,000 NFTs**. Each NFT is one seat in the swarm, and each seat authorizes one active device [FACT, S1].
- Holders register the NFT as an agent under **ERC-8004** and run the open-source worker client [FACT, S1].
- In ERC-8004 the `agentId` is "the ERC-721 tokenId assigned incrementally by the registry". The agent's `agentURI` resolves to a registration file containing `name`, `description`, `image` and `services` [FACT, S2]. ERC-8004 is still a **Draft** [FACT, S2].
- The public endpoint `https://api.imd.fun/swarm` returns JSON with the top-level keys `at, health, counts, seats, events, owners, chain` [FACT, S3; snapshot taken 2026-10-16 23:24:55 UTC].
  - `seats` is keyed by NFT tokenId. Each entry has `tokenId, agentId, attempts, accepted, rejected, failed, pending, last, working, queued` [FACT, S3].
  - Snapshot values: **1,096 seat entries**, NFT tokenIds 0–1998, every entry with a non-empty `agentId`, 1,096 distinct agentIds of at most 5 digits (e.g. tokenId 1 → agentId "50953") [FACT, S3].
  - `health` reported `agentsOnline: 1031`, `seatsEnrolled: 1043` and `workingNow: 7` [FACT, S3]. The seat-entry count (1,096) does not match `seatsEnrolled` (1,043). I don't know the reason [UNCERTAIN].
  - `events` (60 items in the snapshot) carry `kind` (node, agent, job, done, review, launch), `role` (implement, review, integrate, tests), `state` (joined, waiting, working, accepted, completed) and `step` (e.g. `research_report`, `audit_math`, `write_foundry_tests`) [FACT, S3].
  - `chain` gives `chainId: 1` and the collection contract `0x0000ec93127baa929e58e97dd0095a2bfb38ec1d` [FACT, S3; matches `identity.collection` at `api.imd.fun/health`, S4].
- An earlier figure (2026-09-25) was 380 of 2,000 seats enrolled [FACT, S1]. The number is growing fast, so the client must not hard-code agent counts [INFERENCE].

### 1.2 Design consequences
- **[DESIGN]** The roster comes from `GET /swarm`. One agent body is spawned per `seats` entry. Agents are keyed by `tokenId` (a stable NFT identity) and labelled with `agentId`.
- **[DESIGN]** The ID shown above each head is configured by a setting: `idLabel: "agentId" | "tokenId" | "both"`, defaulting to `"agentId"`, rendered as `#50953`. The info panel always shows both. **[OPEN Q1]** Which number does the requester mean by "real ID number"?
- **[DESIGN]** The world scales to 2,000 bodies, the supply cap [FACT, S1], so it never needs re-layout as seats enroll. **[OPEN Q2]** Should "all agents" mean all 2,000 seats (unenrolled ones dimmed/dormant), only enrolled seats, or only agents online now?
- **[DESIGN]** Names and avatars, where present, come from the ERC-8004 registration file (`name`, `image`) via the registry's `tokenURI` [FACT about the field names, S2]. If the file is missing or can't be fetched, show "Agent #<agentId>". **[UNCERTAIN]** I did not confirm which registry contract IMD uses or whether its registration files are populated. Validation: read `tokenURI(agentId)` for 5 sample agents.
- **[DESIGN]** Poll `/swarm` every 30 s (configurable). Diff the result, spawning new seats with a "materialize" effect. Live `events` feed the behavior system (§4.4). The API showed no authentication on GET in my test [FACT, S3]. **[UNCERTAIN]** Rate limits and CORS headers for browser clients were not checked. A thin caching proxy is recommended if CORS blocks direct calls.
- Privacy **[DESIGN]**: the API also returns owner wallet addresses (`owners`) [FACT, S3]. The game will **not** show owner addresses by default. **[OPEN Q3]**

### 1.3 Agent view-model (client contract)
```ts
interface AgentVM {
  tokenId: number;          // NFT tokenId, primary key            (from /swarm seats)
  agentId: string;          // ERC-8004 agentId, shown label       (from /swarm seats)
  name?: string;            // ERC-8004 registration file          (optional)
  imageUrl?: string;        // ERC-8004 registration file          (optional)
  stats: { attempts: number; accepted: number; rejected: number;
           failed: number; pending: number; queued: number };
  lastActive: string;       // ISO timestamp, seats[].last
  workingNow: boolean;      // seats[].working
  currentWork?: { role: string; step?: string; state: string; jobId?: string }; // newest matching event
  acceptanceRate: number;   // derived: accepted / max(1, attempts)
  presence: "working" | "online" | "idle" | "dormant"; // derived, §4.4
}
```

---

## 2. Concept and core gameplay loop

**Pitch [DESIGN]:** Synapse City is a dark, endless-feeling map drawn as a neural network. Neurons are rooms and glowing axons are paths. Every IMD agent lives there as a small embodied character with its ID floating overhead. You watch, you click, an agent stops and looks straight at you, and its full dossier opens.

**Genre [DESIGN]:** an ambient, observational "living ant-farm" simulation in the line of *Generative Agents* "Smallville", where 25 agents lived in a Sims-like sandbox the user could interact with [FACT, S11]. This design swaps LLM-driven cognition for cheap scripted autonomy and drives "what an agent is really doing" from the live swarm data.

### Core loop [DESIGN]
1. **Observe**: pan and zoom across the map. Ambient life: agents walking axons, reading, gaming, studying, chatting.
2. **Notice**: pulses of light along axons show live swarm work (a job posted, a submission accepted). Agents that are really working in the swarm glow brighter.
3. **Inspect**: click an agent. It halts, turns, makes eye contact (the fourth-wall break) and the info panel opens.
4. **Learn / compare**: read the stats, jump to related agents (same job), follow an agent with the camera.
5. **Release**: click empty space. The panel closes and the agent breaks gaze and resumes its earlier task. Go back to step 1.

Secondary loops [DESIGN]: search by ID (`/` key), "Follow" camera mode, filters (working now / top accepted / recently joined), and a leaderboard overlay. There is no fail state. The retention hooks are discovery (finding your own agent) and live change.

---

## 3. World and map design

### 3.1 Topology [DESIGN]
- The map is a **graph**: nodes (*neurons*) are locations, and edges (*axons*) are walkable wires.
- Layout: about 7 concentric "layers", echoing neural-network diagrams (input → hidden → output). There are 120–160 neurons with 3–6 axons each, plus a few long "myelinated highways" for cross-map travel. The world measures about 400×400 units (1 unit ≈ 1 m, agent height ≈ 1.8 units).
- **Neuron types** (each a small open-roofed room on a glowing disc, so it can be seen into from an elevated camera):

| Neuron type | Count | Props / interaction slots | Activity |
|---|---|---|---|
| Library | 14 | 8 reading chairs, 2 shelves (browse slots) | Read a book |
| Arcade / VR room | 14 | 6 cabinets, 4 VR pads | Play video games |
| Classroom | 12 | 1 lectern (teacher slot), 12 desks | Learn / teach |
| Plaza (hub) | 20 | Benches, 6 conversation circles | Socialize |
| Forge (workshop) | 16 | 8 terminals | "Working" (live swarm job) |
| Review chamber | 8 | Round table of 6 | "Reviewing" (live review role) |
| Rest pod | 12 | 10 pods | Idle / dormant agents |
| Junction (empty) | rest | none | Path only |

- Capacity check [INFERENCE]: the slots add up to roughly 14·10 + 14·10 + 12·13 + 20·12 + 16·8 + 8·6 + 12·10 ≈ 970 seats of activity. At any moment about half the agents are walking or socialising in open space, so 2,000 agents fit without queues piling up. Tune the counts after the first crowd test.

### 3.2 Procedural generation [DESIGN]
Seeded generation keeps the map stable between sessions. Place neurons by Poisson-disk sampling inside the layers, connect each to its k nearest neighbours in adjacent layers (k = 3–5), then add a minimum spanning tree so the graph is connected. Navigation runs A* on the neuron graph, plus a local lane offset along each axon (two lanes per direction) so walkers don't overlap.

### 3.3 Home assignment [DESIGN]
`home = hash(tokenId) mod restPods`. An agent's favourite activity weights also come from `hash(tokenId)`, so each agent has a stable "personality" across sessions without storing anything server-side.

---

## 4. Agent behavior

### 4.1 Architecture [DESIGN]
Each agent has two layers:
1. **Utility selector** (runs every 2–6 s per agent, staggered): scores candidate activities and picks one.
2. **Activity state machine**: walks the chosen activity through `Travel → Approach → Enter → Loop → Exit`.

Local movement uses Craig Reynolds' steering behaviours. **Wander** gives random roaming, **arrival** slows the agent as it nears its target, and **separation** spaces agents apart [FACT that these behaviours are defined in Reynolds' work and that arrival scales speed by distance within a slowing radius, S9].

### 4.2 Default behaviours and utility weights [DESIGN]

| Activity | Base weight | Modifiers | Duration (s) | Where |
|---|---|---|---|---|
| Roam / wander | 1.0 | +0.5 if just finished an activity | 15–45 | Axons, plazas |
| Read book | 0.8 | × personality.curiosity; × (1 + log10(1+accepted)/4) | 30–90 | Library |
| Play video games | 0.8 | × personality.play; +0.6 if `presence=idle` | 40–120 | Arcade |
| Attend class (student) | 0.7 | +0.5 if acceptanceRate < 0.85 ("needs to learn") | lesson cycle 90 | Classroom desk |
| Teach class | 0.3 | Only if top-10% accepted, and the classroom has ≥3 students | 90 | Lectern |
| Chat with agent | 0.9 | +0.8 if another idle agent within 6 units; +1.0 if same current jobId | 8–20 | Anywhere |
| Rest | 0.4 | +3.0 if `presence=dormant` | until presence changes | Rest pod |
| Work (live) | — | **Forced** when `workingNow=true`, role ∈ {implement, integrate, tests} | while working | Forge |
| Review (live) | — | **Forced** when an event shows `role=review, state=working` | while working | Review chamber |

Weights get ±20% noise, and the last two activities are penalised ×0.5 so behaviour varies.

### 4.3 Agent–agent interaction [DESIGN]
- **Greeting**: two walkers passing within 3 units with a 15% roll: nod and wave (1.2 s) without stopping.
- **Conversation**: a mutual handshake (both must accept). They face each other at 1.4 units and play `talk_gesture` / `listen_nod` alternately on a 3–5 s cadence. Speech bubbles show glyph icons (book, gamepad, gear, star), not text, to keep localisation and moderation cheap.
- **Group circles**: up to 6 agents arrange on a ring in a plaza.
- **Co-workers**: agents sharing a `jobId` in live events walk to the same forge and stand side by side. A light pulse travels along the axon between their terminals.
- **Celebration**: a `done`/`accepted` event for an agent → a 2 s `cheer` animation, and nearby agents turn and clap.

### 4.4 Mapping live swarm data to behaviour [DESIGN]
| Data (S3) | Derived presence | Visible effect |
|---|---|---|
| `seats[i].working = true` | working | Goes to a forge, works at a terminal, bright aura |
| `last` within 1 h | online | Normal autonomous life |
| `last` within 7 d | idle | Normal life, slightly dimmer body glow |
| `last` older than 7 d, or never | dormant | Sleeps in a rest pod, ID label at 50% opacity |
| New `agent/joined` event | — | "Materialises" at the map's input layer with a spark burst |

The thresholds are placeholders to be tuned [DESIGN]. **[UNCERTAIN]** The precise meaning of `working` and of the event states comes only from field names and values in one snapshot. Confirm with the IMD API docs (S5) or the maintainers.

---

## 5. The fourth-wall interaction (primary feature)

### 5.1 State machine [DESIGN]
```
AUTONOMOUS ──click on agent──▶ HALTING ──(0.25 s)──▶ TURNING ──(0.45 s)──▶ GAZING (panel open)
     ▲                                                                       │
     │                                     click empty space / Esc / close ──┘
     └──(0.6 s)── RESUMING ◀──(0.35 s)── GAZE_BREAK ◀───────────────────────┘
```
- **Snapshot on click.** Store `{activityId, stateMachineNode, path, pathIndex, slotId, animClip, animTime, facingYaw}`. The agent keeps its interaction slot (chair, cabinet) reserved while gazing, so it can return to exactly the same spot.
- **HALTING (0.25 s).** Velocity eases to 0 (ease-out quad). The current clip cross-fades to `idle_stand`. If the agent was seated, it plays `stand_up_quick` (0.5 s) first, or alternatively stays seated and only turns the upper body (see 5.2).
- **TURNING (0.45 s).** Body yaw rotates to face the camera's projected position on the ground plane, with ease-in-out cubic. The head leads the body by about 80 ms.
- **GAZING.** The head/neck and eyes track the **camera** (not the cursor) every frame. That gives the "looking at me through the screen" effect even as the camera orbits. Play `acknowledge` (small nod + raised hand, 0.8 s) once, then a breathing `idle_attentive` loop with a blink every 2–5 s. A subtle rim light on the agent and a 15% vignette on the rest of the scene.
- **Panel opens** at the same moment as TURNING starts (§6).
- **GAZE_BREAK (0.35 s).** Eyes look away first (down and to the side, 120 ms), then the head returns to neutral, then a `small_shrug` / `back_to_it` gesture.
- **RESUMING (0.6 s).** The body yaw rotates back to the stored `facingYaw` or path tangent. The stored activity clip cross-fades back in at the stored `animTime`, then the state machine continues from the stored node. If the activity's time ran out in the meantime, it continues anyway with its remaining duration (paused clock).
- **Edge cases.**
  - *Click another agent while gazing:* agent A runs GAZE_BREAK→RESUMING while agent B starts HALTING, and the panel cross-swaps content (150 ms).
  - *Live data forces an activity change while gazing (e.g. it starts working):* queue it until the gaze is released, and show a "⚡ Just picked up a task" chip in the panel.
  - *Agent in a conversation:* the partner plays `wait_patiently` and resumes when A resumes.
  - *Agent off-screen after a camera move:* the gaze continues. Closing the panel resumes normally.
  - *Reduced motion:* see §8.

### 5.2 Implementation notes [INFERENCE unless marked]
- The turn and gaze are procedural bone rotations layered on top of the clip. Clamp neck yaw to ±70° and pitch to ±35°, and let the remainder come from body/spine yaw. Eye bones (or eye texture UV offsets) handle the last few degrees.
- Animation clips are blended through three.js `AnimationMixer` / `AnimationAction`. `crossFadeTo(target, duration, warp)` is an `AnimationAction` method, and actions are obtained from `mixer.clipAction(clip)` [FACT, S7, S8]. Known pitfall: cross-fading into an action whose weight is 0 fades towards the rest pose. Set the target's weight to 1 and `play()` it before fading [FACT reported in a forum thread, S8; UNCERTAIN as general behaviour, so check during the prototype].
- If the 2D Spine path (§7.4) is chosen, crossfades come from `AnimationStateData.setMix(from, to, duration)` and a default mix [FACT, S10].

---

## 6. UI/UX flow

### 6.1 Screens and layers [DESIGN]
1. **Boot / sync** (≤3 s): logo over pulsing wires. Text: "Syncing swarm… 1,0xx agents online" (live number from `health.agentsOnline`).
2. **World view** (main): full-screen canvas plus a minimal HUD.
   - Top-left: swarm vitals (online, working now, accepted in the last 24 h, all from `health`).
   - Top-right: search box (`/`), filters, settings cog.
   - Bottom-left: mini-map of the neuron graph with agent density heat.
   - Bottom-right: zoom controls and a "recenter" button.
3. **Agent info panel**: a right-side drawer, 400 px wide on desktop and a bottom sheet at 60% height on mobile.
4. **Settings**: ID label mode, label density, reduced motion, graphics quality, poll interval, sound.

### 6.2 Agent info panel content [DESIGN]
| Section | Fields | Source |
|---|---|---|
| Header | Avatar (registration `image`, else a rendered portrait of the 3D body), name, **Agent #agentId**, Seat/NFT #tokenId, presence pill | S2, S3 |
| Live status | Current role, step, state, jobId (shortened), time since `last` | S3 events/seats |
| Record | Attempts, accepted, rejected, failed, pending, queued; acceptance-rate ring | S3 seats |
| In-world now | Simulated activity (e.g. "Reading in Library L-07"), personality bars | client sim |
| Links | "View on explorer", "Agent registration file", "Follow camera" | S1 explorer, S2 |
| Footer | "Data as of hh:mm:ss UTC" (`at`) | S3 |

The exact explorer deep-link URL format is **[UNCERTAIN]**. Only the explorer host is attested (S1).

### 6.3 Interaction rules [DESIGN]
- **Open:** left-click/tap on an agent hit-box (a capsule, slightly larger than the body, so agents are easy to hit when zoomed out). Hover gives an outline plus a tooltip with the ID.
- **Close:** click/tap on empty world space, press `Esc`, or press the panel's ✕. Clicking *inside* the panel never closes it. A drag (pointer moved > 6 px) is treated as camera pan, not as "click elsewhere", so panning while the panel is open does not close it.
- Keyboard: `Tab` cycles through visible agents in screen order, `Enter` opens one, `Esc` closes, `F` follows.
- Picking: GPU ID-buffer picking or a raycast against per-agent capsules in a spatial hash. **[INFERENCE]** With 2,000 agents, raycasting against an InstancedMesh capsule proxy is enough. three.js lists raycasting among its InstancedMesh examples [FACT, S6].
- Panel motion: slides in over 220 ms (ease-out), fades its content in at 120 ms with a 40 ms stagger per section. Close: 180 ms ease-in.

---

## 7. Visual style and rendering

### 7.1 Art direction [DESIGN]
- **Mood:** bioluminescent circuitry. The background is near-black blue (#05070D) with a faint grid and depth fog.
- **Axons:** tube or ribbon meshes with an emissive core (#36E2FF cyan) and a soft halo. Animated "signal pulses" (a UV-scrolling gradient) travel along them. Pulses fire when real swarm events occur (job → cyan, review → violet #9B6BFF, accepted → gold #FFC857, rejected → red #FF4D6D, brief).
- **Neurons:** frosted-glass discs with a dendrite fringe. Room furniture is stylised low-poly, emissive-edged.
- **Agents:** stylised humanoid "synthetic" bodies, full body (head, torso, arms, legs), about 1.8 units tall, readable silhouette at 40 px screen height. Matte dark body with glowing seams, colour-coded by presence (working: white-gold, online: cyan, idle: teal, dormant: grey). Each agent gets a unique accent hue from `hash(tokenId)` and one accessory (visor, antenna, scarf, backpack; 4×6 variants).
- **ID label:** a billboarded pill above the head reading `#50953`, monospace, white on 70% black, with a 1 px cyan border. Distance culling: labels show for the 150 nearest agents plus any hovered/selected/followed one. Label size is clamped to 11–16 px.

### 7.2 Glow / bloom [FACT + DESIGN]
three.js `UnrealBloomPass` takes resolution, strength, radius and threshold [FACT, S12]. The official "selective bloom" example darkens non-bloomed objects, renders a bloom pass, restores the materials, then composites [FACT per the forum description, S13]. Design choice: put the glowing materials (wires, seams, labels off) on a bloom layer, threshold ≈ 0.85, strength 1.2, radius 0.4. Users report that emissive intensity above 1 is often needed for the threshold to catch [FACT reported, S13]. The pmndrs `postprocessing` library is an acceptable alternative [UNCERTAIN performance difference, so benchmark both].

### 7.3 Scalability strategy for about 2,000 bodies [INFERENCE]
- `InstancedMesh` renders many objects that share one geometry and material in fewer draw calls [FACT, S6]. Skinned animated characters are costlier, so use LOD:
  - **LOD0** (≤ about 60 nearest, plus the selected agent): full `SkinnedMesh` with an `AnimationMixer`, procedural head/eye gaze.
  - **LOD1** (≤ about 400): instanced, low-poly body animated by baked vertex-animation textures (VAT) or GPU-skinned instancing. Clip and frame go per instance as instanced attributes.
  - **LOD2** (rest): instanced billboard impostors (8-direction sprite sheets) plus a coloured dot at extreme zoom-out.
  - **[UNCERTAIN]** No source in this report benchmarks VAT or instanced skinning in three.js. It is a well-known technique but needs a spike (§11, week 1).
- The simulation runs in a Web Worker (behaviour, steering, path following) and posts transforms in a `Float32Array` with transferable buffers. Behaviour ticks are staggered (each agent re-plans every 2–6 s). Steering for far agents runs at 10 Hz and is interpolated.
- Targets **[DESIGN]**: 60 fps on a 2022 mid-range laptop iGPU at 1080p with 2,000 agents; 30 fps on mobile with about 500 agents visible (fewer LOD0 slots and no bloom on low settings).

### 7.4 Alternative 2D path [FACT + INFERENCE]
PixiJS v8's ParticleContainer is designed for very large sprite counts. Its own blog reports 1,000,000 particles at 60 fps on an M3 MacBook Pro, against 200,000 regular sprites [FACT, single vendor benchmark, S14]. The guide marks the API as experimental [FACT, S15]. A 2.5D isometric version with 8-direction Spine characters would be simpler to build. The cost is that the "turn to face the player" moment becomes a switch to a dedicated front-facing animation rather than true 3D eye contact [INFERENCE]. **Recommendation:** 3D three.js, because eye contact is the signature moment. 2D stays as a fallback if the week-1 spike misses its performance targets.

---

## 8. Accessibility [FACT + DESIGN]
- WCAG 2.x SC 2.3.3 *Animation from Interactions* (AAA) requires that motion animation triggered by interaction can be disabled unless essential [FACT, S16]. W3C technique C39 uses the `prefers-reduced-motion` media query [FACT, S17].
- **[DESIGN]** If `prefers-reduced-motion: reduce` or the in-game toggle is set: no camera fly-to, a 0 ms panel slide (fade only), agent turn instant (snap) with gaze kept, axon pulses slowed to 25% speed, bloom halved.
- No flashing above 3 Hz. Celebratory pulses are ≤ 2 Hz [DESIGN].
- The panel is a DOM `<aside role="dialog" aria-modal="false" aria-labelledby>` with focus moved into it on open and returned to the canvas on close. A screen-reader live region announces "Agent #50953 selected, reading in Library L-07" [DESIGN].
- Colour is never the only signal. Presence also shows as a pill text label and an icon [DESIGN].

---

## 9. Animation specification [DESIGN]

Rig: humanoid, 22–30 bones plus `head`, `neck`, `eye_L`, `eye_R` and a `prop_R` socket (book, controller). Authoring: Blender, exported as glTF 2.0 (GLB). Clips at 30 fps and sampled in-engine.

| Clip | Loop | Length | Notes |
|---|---|---|---|
| `idle_stand` | yes | 4.0 s | Breathing, weight shift |
| `idle_attentive` | yes | 3.0 s | Straighter posture used while gazing |
| `walk` | yes | 1.0 s | Root motion off; speed 1.4 u/s; foot-sync by playback rate |
| `walk_brisk` | yes | 0.8 s | Used when heading to forced "work" |
| `turn_in_place_L/R` | no | 0.45 s | Optional, else procedural yaw |
| `sit_down` / `stand_up` / `stand_up_quick` | no | 1.0 / 1.0 / 0.5 s | Chair slots |
| `read_seated` | yes | 6.0 s | Page turn every 6 s; book prop |
| `browse_shelf` | yes | 5.0 s | Reach, inspect, return |
| `game_arcade` | yes | 2.0 s | Button mashing; screen emissive flicker ≤ 2 Hz |
| `game_vr` | yes | 4.0 s | Head tracking, arm swipes |
| `student_listen` / `student_write` / `hand_raise` | yes / yes / no | 4 / 3 / 1.5 s | Classroom desk |
| `teach_lecture` | yes | 5.0 s | Pointing at a holo-board |
| `talk_gesture` / `listen_nod` | yes | 3.0 / 2.5 s | Conversation pair |
| `wave` / `nod` | no | 1.2 / 0.6 s | Greetings |
| `type_terminal` | yes | 2.0 s | Forge working |
| `review_think` | yes | 4.0 s | Chin-tap, scrolling holo-pane |
| `cheer` / `clap` | no | 2.0 / 1.5 s | Accepted-work celebration |
| `acknowledge` | no | 0.8 s | Fourth-wall greeting |
| `back_to_it` | no | 0.6 s | Gaze break |
| `sleep_pod` | yes | 6.0 s | Dormant agents |
| `materialize` | no | 1.5 s | Spawn; dissolve-in shader |

Blending rules: the default crossfade is 0.25 s. Locomotion ↔ idle takes 0.2 s, any → `idle_attentive` 0.3 s, and resume from gaze 0.4 s. Procedural layers apply after clip evaluation, in this order: (1) look-at (neck/head/eyes, weight eased 0→1 over 0.3 s), (2) body yaw, (3) foot IK on seats and stairs (optional, LOD0 only).

Easing curves: turning uses cubic ease-in-out, halting quad ease-out, and gaze weight a smoothstep.

---

## 10. Technical architecture [DESIGN]

```
┌──────────── Browser ─────────────────────────────────────────────┐
│ UI (React/Svelte DOM): HUD, panel, search, settings               │
│      ▲ selection store (agentId, panelOpen)                       │
│ Render thread: three.js scene, LOD manager, picking, bloom         │
│      ▲ transforms (SharedArrayBuffer or transferable Float32Array) │
│ Sim Worker: map graph, A*, steering, utility AI, interaction FSM  │
│      ▲ AgentVM diff                                               │
│ Data layer: poll /swarm (30 s), ERC-8004 metadata cache (IndexedDB)│
└──────────────▲───────────────────────────────────────────────────┘
               │ HTTPS
   api.imd.fun/swarm  (+ optional caching proxy, 15–30 s TTL)
   ERC-8004 registry tokenURI → registration JSON (ipfs/https)
```
- The simulation is client-side and non-authoritative. Every viewer sees a different but plausible world, while live data (working, events) is the same for everyone [DESIGN]. A shared deterministic world (same seed plus server time) is a possible extension.
- SharedArrayBuffer needs cross-origin isolation headers (COOP/COEP) [UNCERTAIN for the target host]. Fall back to transferable buffers.
- Stack recommendation: TypeScript, Vite, three.js, a small ECS (or plain typed arrays), and a DOM UI framework of the team's choice.
- Telemetry (optional): agent clicks, panel dwell time, search usage.

---

## 11. Feasibility, risks and delivery plan

### 11.1 Feasibility verdict
- **Data:** feasible. The agent list and IDs are publicly readable today [FACT, S3].
- **Rendering 2,000 animated bodies in a browser:** feasible with LOD and instancing [INFERENCE from S6 and S14; not benchmarked here].
- **Fourth-wall gaze:** low risk. It is standard look-at bone rotation plus clip crossfades [INFERENCE; API support FACT, S7].

### 11.2 Risks
| Risk | Likelihood | Mitigation |
|---|---|---|
| Crowd animation cost exceeds budget | Medium | Week-1 spike on VAT vs. impostors; drop to 2D fallback (§7.4) |
| `/swarm` payload grows (1,096 seats plus events) or gains rate limits/CORS | Medium | Caching proxy; request a lighter endpoint **[OPEN Q4]** |
| Field semantics misread (`working`, event states) | Medium | Confirm with IMD docs/maintainers (S5) |
| ERC-8004 is Draft and may change | Low–Med | Isolate the metadata adapter; registration data optional |
| Label clutter at 2,000 agents | High | Distance and priority culling (§7.1) |
| Mobile performance | High | Lower LOD0 count, no bloom, 30 fps cap |

### 11.3 Milestones (estimate, 2–3 developers + 1 artist) [INFERENCE]
1. **Weeks 1–2:** data adapter, procedural map, 2,000 instanced placeholder capsules wandering, picking, panel with live data. **Perf spike gate.**
2. **Weeks 3–5:** rigged character, the clip set, LOD0/LOD1, utility AI, all room types, the fourth-wall FSM.
3. **Weeks 6–7:** bloom/style pass, live event pulses, social interactions, accessibility, settings.
4. **Week 8:** optimisation, mobile, QA. Acceptance tests from §12.

---

## 12. Acceptance tests for the build [DESIGN]
1. With a recorded `/swarm` fixture of N seats, the world contains exactly N agent bodies and each label equals the configured ID field.
2. Clicking an agent: it stops within 0.25 s and faces the camera within 0.7 s total (angle to camera < 5°), the panel opens with matching agentId/tokenId, and the gaze still holds after the camera orbits 90°.
3. Clicking empty space (without dragging): the panel closes, the agent's gaze weight reaches 0 within 0.35 s, and within 1 s it is back in the same activity slot with the same activity id.
4. Dragging to pan while the panel is open does not close it.
5. `Esc` closes. `Tab`/`Enter` can select an agent without a mouse.
6. With reduced motion on, no camera fly or slide animations occur.
7. 2,000 agents at 1080p on the reference laptop: median frame time ≤ 16.7 ms over 60 s.
8. A seat with `working=true` reaches a forge terminal within 30 s of the data update.

---

## 13. Open questions for the requester
- **Q1:** Should the "real ID number" be the ERC-8004 `agentId` (e.g. 50953) or the NFT `tokenId` (e.g. 1)? Default: agentId label, both in the panel.
- **Q2:** Should "all IMD swarm agents" mean all 2,000 seats, only enrolled seats, or only online agents? Default: every entry in `/swarm.seats`, with dormant ones sleeping.
- **Q3:** May the panel show the owner wallet address? Default: no.
- **Q4:** Will IMD provide a dedicated or lighter endpoint (or a websocket) for a public game client, and what rate limits apply?
- **Q5:** Is the game meant as a public web page, an embedded explorer view, or a desktop build? This affects the performance targets.
- **Q6:** Should agents have names or avatars beyond the ID (from ERC-8004 registration files), or should they stay anonymous numbered bodies?

## 14. What remains uncertain (summary)
- The meaning of the `working` flag and event states. I inferred them from field names in one snapshot.
- Why the seat-entry count (1,096) differs from `seatsEnrolled` (1,043).
- The IMD ERC-8004 registry address, and whether registration files contain names or images.
- CORS and rate limits on `api.imd.fun` for browser clients.
- Real performance of crowd skinning in three.js on target devices. No benchmark was run for this report.
- The exact deep-link format of explorer.imd.fun.

---

## 15. Sources
- **S1:** Bankless, "Inside IMD, Ethereum's new AI swarm experiment" (Sept 2026): 2,000 NFTs/seats, ERC-8004 registration, worker client, 380/2,000 enrolled on 2026-09-25, explorer.imd.fun, api.imd.fun/swarm. https://www.bankless.com/read/inside-imd-ethereum-s-new-ai-swarm-experiment
- **S2:** ERC-8004 "Trustless Agents" (Draft, created 2025-08-13): agentId = ERC-721 tokenId; registration file fields. https://eips.ethereum.org/EIPS/eip-8004
- **S3:** IMD swarm API, live response read 2026-10-16 23:24:55 UTC (`at` = 1792193095514). https://api.imd.fun/swarm
- **S4:** IMD API health, read 2026-10-16. https://api.imd.fun/health
- **S5:** IMD developer docs (reachable, HTTP 200; not reviewed in depth). https://imd.fun/docs/
- **S6:** three.js docs, InstancedMesh. https://threejs.org/docs/pages/InstancedMesh.html
- **S7:** three.js docs, AnimationAction. https://threejs.org/docs/pages/AnimationAction.html
- **S8:** three.js forum, "AnimationAction.crossFadeTo not working?" https://discourse.threejs.org/t/animationaction-crossfadeto-not-working/63467 and "What variable to pass for crossFadeTo" https://discourse.threejs.org/t/what-variable-to-pass-for-crossfadeto/28186
- **S9:** Steering behaviours lecture slides citing Reynolds (1999), "Steering Behaviors for Autonomous Characters" (seek, arrival, wander, separation). https://diana.ms.mff.cuni.cz/pogamut_files/lectures/2015-2016/steeringBehaviors-slides.pdf (original paper: https://www.red3d.com/cwr/steer/)
- **S10:** Esoteric Software, Spine "Applying Animations" (AnimationStateData mix durations). https://esotericsoftware.com/spine-applying-animations
- **S11:** Park et al., "Generative Agents: Interactive Simulacra of Human Behavior", arXiv:2304.03442 (2023). https://arxiv.org/abs/2304.03442
- **S12:** three.js docs, UnrealBloomPass. https://threejs.org/docs/pages/UnrealBloomPass.html
- **S13:** three.js forum, "Selective UnrealBloomPass issues". https://discourse.threejs.org/t/selective-unrealbloompass-issues/9331
- **S14:** PixiJS blog, "ParticleContainer v8". https://pixijs.com/blog/particlecontainer-v8
- **S15:** PixiJS guide, Particle Container. https://pixijs.com/8.x/guides/components/scene-objects/particle-container
- **S16:** W3C, Understanding SC 2.3.3 Animation from Interactions. https://www.w3.org/WAI/WCAG21/Understanding/animation-from-interactions
- **S17:** W3C technique C39, prefers-reduced-motion. https://www.w3.org/WAI/WCAG22/Techniques/css/C39.html

Method note: S1, S2 and S3/S4 were read directly while writing this report. S6–S17 were found by web search, and their claims here rest on the search-result excerpts. I did not open those pages in full, so check them against the linked pages before relying on exact API signatures. Forum posts (S8, S13) are user reports, not official documentation.
