Skip to content

Commit 43cd535

Browse files
authored
Add Phase 1 React + Three.js 3D floor plan viewer (#15) (#16)
Implements Phase 1 of the frontend redesign tracked in #15: a Tiled-JSON parser and a full 3D Three.js floor plan viewer with Kenney furniture models, extruded walls, textured floors, and interactive camera controls. New package: environment/react_frontend React 18 + Vite + TypeScript + Three.js (@react-three/fiber + drei) Parser (src/parser/) Pure pipeline that reads the legacy Tiled JSON maps and the three special-block CSV lookup tables (arena, game-object, spawning) and emits a strongly-typed MapLayout: - Zone extraction via iterative 4-way flood fill on the Arena Layer, producing one ZoneRegion per contiguous component with a clockwise perimeter polygon, bounding box, and centroid. - Equipment extraction from the Object Interaction Layer. - Spawning-slot extraction from the Spawning Blocks layer. - Wall compression into perimeter-only axis-aligned segments (only edges bordering a non-wall tile are emitted — no interior grid artefacts inside solid wall blocks). - Collision mask as a boolean[y][x] grid. - loadMapLayout(): browser-side fetch helper that drives the parser. Three.js 3D renderer (src/components/ThreeFloorPlan.tsx) Full 3D scene built with @react-three/fiber: - Zone floors: PlaneGeometry per zone region, textured with raster PNGs (wood planks for the waiting room, polished concrete for clinical areas) and colour-tinted per zone category so rooms stay visually distinguishable. - Walls: BoxGeometry extruded along every wall segment with 2.5-unit height, white MeshStandardMaterial, real shadow casting. - Furniture: 7 Kenney Furniture Kit 2.0 (CC0) GLTF models loaded via useGLTF — bedSingle, chairCushion, loungeChair, computerScreen, table, desk, chairDesk — placed at each equipment tile position, casting and receiving shadows. - Lighting: DirectionalLight (2048 px shadow map, warm colour) plus soft AmbientLight fill, positioned for consistent top-left illumination. - Camera: OrbitControls — drag to orbit, scroll to zoom, right-drag to pan, with clamped polar angle and distance limits. - Navigation overlay: Google Maps-style HTML buttons (zoom +/−, rotate left/right, reset camera) positioned top-right of the canvas, wired to programmatic camera control via exposed OrbitControls ref. - Ground plane: large dark surface surrounding the building. Viewer page (src/MapViewer.tsx) - Sidebar with map catalogue (Small ED Layout 30×20, Foothills ED Layout 122×123) plus display toggles (zone labels, spawning slots). - Live parsed-count panel (zones, equipment, spawning, walls, map dimensions). - URL query-string state so a map selection survives reloads. Assets - 7 Kenney GLTF furniture models in public/models/ (~110 KB total, CC0 licence, kenney.nl). - 5 Kenney isometric sprite PNGs in src/assets/furniture/ (CC0, used as reference / future 2D fallback). - 2 floor texture PNGs in src/assets/textures/ (wood + polished concrete, generated at build time via scripts/generate-assets.mjs). - Asset catalogue (src/assets/index.ts) maps each ZoneId to its texture URL for the Three.js TextureLoader. Tests - 42 vitest unit tests across CSV decoders, loadMapLayout (with mocked fetch), and parseTiledJSON. Fixture-pinned counts for both seed maps: small_ed_layout (8 zones, 42 equipment, 18 spawning, 38 wall segments) and foothills_ed_layout (26 zones, 219 equipment, 70 spawning, 774 wall segments). - 6 Playwright e2e tests: 3D view rendering, map switching, URL persistence, display toggles, and navigation control visibility. - CAPTURE_SCREENSHOTS=1 gated spec produces visual spot-check PNGs for both maps. Tooling & CI - run_map_viewer.sh: single-command launcher at the repo root (dev / build / preview / test / test:e2e / generate-assets). - New test-react-frontend GitHub Actions job: typecheck, lint, vitest, production build, Playwright install + e2e, with Playwright report upload on failure. - ESLint flat config, strict tsc, vitest coverage wiring. - scripts/generate-assets.mjs: Playwright-based build-time PNG generator for floor textures (deterministic Mulberry32 PRNG, value noise, fBm mottle, directional lighting, pixel noise). - scripts/dump-parser.mjs: CLI utility to print parser output. Documentation - environment/react_frontend/README.md: architecture, Three.js scene hierarchy, model mapping table, commands, how to add maps. - Root README.md and CLAUDE.md link to the new viewer. The legacy Django/Phaser frontend under environment/frontend_server/ is untouched and remains the active runtime for the simulator. The React frontend reads its map data directly from the same Tiled assets, so any change to the legacy asset tree shows up in the 3D viewer immediately — there is no copy step.
1 parent 7f1f735 commit 43cd535

74 files changed

Lines changed: 13644 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,48 @@ jobs:
4747
env:
4848
DJANGO_SETTINGS_MODULE: frontend_server.settings.base
4949
PYTHONPATH: reverie/backend_server:environment/frontend_server:analysis
50+
51+
test-react-frontend:
52+
name: React Frontend (Phase 1)
53+
runs-on: ubuntu-latest
54+
defaults:
55+
run:
56+
working-directory: environment/react_frontend
57+
58+
steps:
59+
- uses: actions/checkout@v4
60+
61+
- uses: actions/setup-node@v4
62+
with:
63+
node-version: '20'
64+
cache: npm
65+
cache-dependency-path: environment/react_frontend/package-lock.json
66+
67+
- name: Install dependencies
68+
run: npm ci
69+
70+
- name: Type-check
71+
run: npm run typecheck
72+
73+
- name: Lint
74+
run: npm run lint
75+
76+
- name: Unit tests (vitest)
77+
run: npm test
78+
79+
- name: Build production bundle
80+
run: npm run build
81+
82+
- name: Install Playwright browsers
83+
run: npx playwright install --with-deps chromium
84+
85+
- name: End-to-end tests (Playwright)
86+
run: npm run test:e2e
87+
88+
- name: Upload Playwright report on failure
89+
if: failure()
90+
uses: actions/upload-artifact@v4
91+
with:
92+
name: playwright-report
93+
path: environment/react_frontend/playwright-report/
94+
retention-days: 7

.github/workflows/docs.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Docs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'docs/**'
8+
- 'mkdocs.yml'
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
pages: write
14+
id-token: write
15+
16+
concurrency:
17+
group: pages
18+
cancel-in-progress: true
19+
20+
jobs:
21+
build:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- uses: actions/setup-python@v5
27+
with:
28+
python-version: '3.12'
29+
cache: pip
30+
31+
- name: Install MkDocs and theme
32+
run: pip install mkdocs-material
33+
34+
- name: Build documentation
35+
run: mkdocs build --strict
36+
37+
- uses: actions/upload-pages-artifact@v3
38+
with:
39+
path: site
40+
41+
deploy:
42+
needs: build
43+
runs-on: ubuntu-latest
44+
environment:
45+
name: github-pages
46+
url: ${{ steps.deployment.outputs.page_url }}
47+
steps:
48+
- id: deployment
49+
uses: actions/deploy-pages@v4

CLAUDE.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,28 @@ Outputs: `analysis/patient_time_metrics.csv` and `analysis/ctas_daily_metrics.cs
122122

123123
## Architecture
124124

125+
### Phase 1 React Floor Plan Viewer (Issue #15)
126+
127+
A new bird's-eye renderer is being built under `environment/react_frontend/`
128+
using **React + Vite + TypeScript + Three.js**. Phase 1 (the Tiled JSON
129+
parser, zone/equipment/wall layers, and a static floor plan canvas) ships
130+
with its own unit + e2e test suites and a single-command launcher:
131+
132+
```bash
133+
./run_map_viewer.sh # install deps + start dev server (http://127.0.0.1:5173)
134+
./run_map_viewer.sh test # vitest unit tests (parser invariants)
135+
./run_map_viewer.sh test:e2e # Playwright e2e tests (viewer interactions)
136+
```
137+
138+
The React frontend reads its map data directly from
139+
`environment/frontend_server/static_dirs/assets/the_ed/`, so any change to
140+
the legacy Phaser asset tree shows up in the new renderer immediately —
141+
there is no copy step.
142+
143+
The legacy Django/Phaser frontend in `environment/frontend_server/` remains
144+
the active runtime; the React frontend lives alongside it as a successor
145+
under construction.
146+
125147
### Component Layout
126148

127149
```
@@ -156,6 +178,16 @@ analysis/
156178
157179
data/
158180
baseline/, surge/ # Pre-computed reference metrics CSVs
181+
182+
environment/react_frontend/ # Phase 1 React + Three.js floor plan viewer
183+
src/parser/ # parseTiledJSON pipeline (issue #15 1.1)
184+
src/components/ # ThreeFloorPlan (Three.js 3D renderer)
185+
src/theme/ # Colour palette
186+
src/data/maps.ts # Catalogue of available Tiled fixtures
187+
src/MapViewer.tsx # Top-level page (sidebar + canvas)
188+
tests/unit/ # vitest parser tests (50)
189+
tests/e2e/ # Playwright viewer tests (6)
190+
README.md # Phase 1 architecture and usage
159191
```
160192

161193
### Simulation State Storage

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ Open [http://localhost:8000/](http://localhost:8000/) to confirm the frontend is
7070

7171
> For interactive mode, custom configuration, or local development, see the sections below.
7272
73+
### Phase 1 Floor Plan Viewer
74+
75+
A new React + Three.js 3D renderer is being built under
76+
[`environment/react_frontend/`](environment/react_frontend/) (tracking
77+
[Issue #15](https://github.com/denoslab/EDSim/issues/15)). To preview just
78+
the floor plan layout from a Tiled map without running the simulation:
79+
80+
```bash
81+
./run_map_viewer.sh # opens http://127.0.0.1:5173
82+
```
83+
84+
See [`environment/react_frontend/README.md`](environment/react_frontend/README.md)
85+
for the parser architecture, validation criteria, and how to add a new map.
86+
7387
## Architecture Overview
7488

7589
EDSim consists of three main components:
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# 3D Floor Plan Viewer
2+
3+
The Phase 1 floor plan viewer lives under `environment/react_frontend/` and provides an interactive 3D rendering of the ED layout.
4+
5+
## Technology stack
6+
7+
| Layer | Technology |
8+
|---|---|
9+
| Build tool | Vite 5 |
10+
| Language | TypeScript 5 (strict mode) |
11+
| UI framework | React 18 |
12+
| 3D renderer | Three.js via `@react-three/fiber` + `@react-three/drei` |
13+
| Unit tests | Vitest |
14+
| E2E tests | Playwright (Chromium) |
15+
| Linting | ESLint 9 (flat config) |
16+
17+
## Scene hierarchy
18+
19+
`ThreeFloorPlan` renders the following hierarchy inside a `<Canvas>`:
20+
21+
1. **GroundPlane** — large dark surface surrounding the building.
22+
2. **ZoneFloor** (×N) — one `PlaneGeometry` per zone region, textured with the raster PNG (wood or concrete) and tinted with the zone colour.
23+
3. **Walls**`BoxGeometry` per wall segment, extruded to 2.5 units height, white `MeshStandardMaterial`, shadow casting enabled.
24+
4. **Furniture** (×N) — Kenney `.glb` models loaded via `useGLTF`, placed at each equipment tile position, casting and receiving shadows.
25+
5. **Lighting**`DirectionalLight` (2048 px shadow map) + warm `AmbientLight` fill.
26+
6. **OrbitControls** — drag/scroll/right-drag camera interaction.
27+
7. **NavControls** — HTML overlay with zoom/rotate/reset buttons.
28+
29+
## Data flow
30+
31+
```
32+
Tiled JSON + CSV files
33+
34+
parseTiledJSON() → MapLayout
35+
36+
ThreeFloorPlan → Three.js scene
37+
38+
Browser WebGL canvas
39+
```
40+
41+
The parser is a pure function with no side effects — it takes the raw Tiled data and emits a strongly-typed `MapLayout` object. The renderer consumes `MapLayout` and builds the 3D scene. This separation means the parser can be tested in isolation (42 vitest tests) and the renderer can be swapped without touching any parsing logic.

docs/architecture/overview.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Architecture Overview
2+
3+
EDSim consists of three main components and an analysis pipeline.
4+
5+
## Component layout
6+
7+
```
8+
reverie/backend_server/ # Simulation engine
9+
reverie.py # ReverieServer — main simulation loop
10+
maze.py # Tile-based map and spatial state
11+
persona/ # Agent classes and cognitive modules
12+
persona_types/ # Doctor, BedsideNurse, TriageNurse, Patient
13+
cognitive_modules/ # perceive, plan, reflect, converse, execute
14+
memory_structures/ # Scratch (short-term), AssociativeMemory (long-term)
15+
prompt_template/ # LLM prompt templates per role
16+
17+
environment/frontend_server/ # Legacy Django + Phaser frontend
18+
translator/views.py # API endpoints
19+
storage/ # Simulation state folders
20+
static_dirs/assets/the_ed/ # Tiled JSON maps and CSV block definitions
21+
22+
environment/react_frontend/ # Phase 1 React + Three.js 3D viewer
23+
src/parser/ # Tiled JSON parser pipeline
24+
src/components/ # ThreeFloorPlan (3D scene)
25+
src/assets/ # Textures and furniture sprites
26+
public/models/ # Kenney GLTF furniture models
27+
28+
analysis/ # Post-simulation metrics
29+
compute_metrics.py # Patient throughput, wait times
30+
```
31+
32+
## Data flow
33+
34+
1. The **simulation engine** reads its initial state from a seed folder under `environment/frontend_server/storage/`.
35+
2. Each simulation step runs the cognitive loop (perceive → retrieve → plan → execute → converse → reflect).
36+
3. Step output is written as JSON files under the simulation's `environment/` and `personas/` directories.
37+
4. The **legacy frontend** reads those JSON files and renders them via Phaser on a Tiled map.
38+
5. The **3D Floor Plan Viewer** parses the same Tiled JSON map files and renders the static floor plan in Three.js — it does not yet consume live simulation output (that's Phase 2).
39+
40+
## Simulation state
41+
42+
All simulation state lives under `environment/frontend_server/storage/<sim_name>/`:
43+
44+
| Path | Contents |
45+
|---|---|
46+
| `reverie/meta.json` | Simulation parameters |
47+
| `reverie/maze_status.json` | Bed count overrides |
48+
| `environment/` | Per-step movement and environment JSON |
49+
| `personas/<name>/` | Agent memory (scratch, associative memory) |
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Simulation Engine
2+
3+
The simulation engine lives under `reverie/backend_server/` and drives the agent-based ED simulation.
4+
5+
## Per-step cognitive loop
6+
7+
Each step in `ReverieServer`:
8+
9+
1. **Perceive** — agents observe nearby agents, objects, and events within `vision_r` tiles.
10+
2. **Retrieve** — relevant memories are fetched from associative memory using embedding similarity + recency weighting.
11+
3. **Plan** — the LLM generates or revises the agent's schedule and immediate next action.
12+
4. **Execute** — the planned action updates tile position and object state.
13+
5. **Converse** — agents in proximity may initiate LLM-driven conversations.
14+
6. **Reflect** — periodically synthesises memories into higher-level insights.
15+
16+
## Agent types
17+
18+
| Role | Behaviour | Spawn zone |
19+
|---|---|---|
20+
| **Doctor** | Assesses patients, orders diagnostic tests, discharges | Major injuries |
21+
| **Bedside Nurse** | Transfers patients between rooms, performs tests | Minor injuries |
22+
| **Triage Nurse** | Assigns CTAS score and injury zone to incoming patients | Triage |
23+
| **Patient** | Arrives with symptoms, interacts with staff | ED entrance |
24+
25+
## LLM integration
26+
27+
`gpt_structure.py` reads `openai_config.json` at import time and constructs the OpenAI/Azure client. All LLM calls go through `run_gpt_prompt.py`, which selects the appropriate prompt template from `persona/prompt_template/ED/`. Transient API errors (429, 500–503) are retried with exponential backoff.
28+
29+
## Configuration (`meta.json`)
30+
31+
Key parameters in `<sim_folder>/reverie/meta.json`:
32+
33+
| Key | Description |
34+
|---|---|
35+
| `sec_per_step` | Simulation seconds per step |
36+
| `patient_rate_modifier` | Scales patient arrival rate |
37+
| `{role}_starting_amount` | Number of agents per role at start |
38+
| `patient_walkout_probability` | Probability a waiting patient leaves |
39+
| `testing_time` / `testing_result_time` | Diagnostic test durations (minutes) |
40+
| `priority_factor` | CTAS score prioritisation weight |

docs/development/adding-maps.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Adding New Maps
2+
3+
The 3D viewer supports any Tiled JSON map that follows the EDSim layer convention. Adding a new map is a four-step process with no code changes required.
4+
5+
## Steps
6+
7+
### 1. Create the Tiled JSON
8+
9+
Use the [Tiled](https://www.mapeditor.org/) map editor. The map must contain these named layers:
10+
11+
| Layer name | Purpose |
12+
|---|---|
13+
| `Arena Layer` | Zone classification (tile IDs from `arena_blocks.csv`) |
14+
| `Object Interaction Layer` | Equipment placement (tile IDs from `game_object_blocks.csv`) |
15+
| `Spawning Blocks` | Agent spawn points (tile IDs from `spawning_location_blocks.csv`) |
16+
| `Walls` | Wall tiles (any non-zero tile = wall) |
17+
| `Collisions` | Collision mask (any non-zero tile = blocked) |
18+
19+
Save the map as a JSON file.
20+
21+
### 2. Place the file
22+
23+
Drop the JSON file into:
24+
25+
```
26+
environment/frontend_server/static_dirs/assets/the_ed/visuals/
27+
```
28+
29+
### 3. Update CSV lookup tables (if needed)
30+
31+
If the new map introduces tile GIDs not already in the existing CSV files, add them to the corresponding file under:
32+
33+
```
34+
environment/frontend_server/static_dirs/assets/the_ed/matrix/special_blocks/
35+
```
36+
37+
| CSV file | Format |
38+
|---|---|
39+
| `arena_blocks.csv` | `<tileId>, ed map, emergency department, <zone label>` |
40+
| `game_object_blocks.csv` | `<tileId>, ed map, <all>, <object label>` |
41+
| `spawning_location_blocks.csv` | `<tileId>, ed map, emergency department, <zone label>, <slot label>` |
42+
43+
### 4. Register in the map catalogue
44+
45+
Add a new entry to `environment/react_frontend/src/data/maps.ts`:
46+
47+
```typescript
48+
{
49+
id: 'my_new_map',
50+
displayName: 'My New Map',
51+
description: '50 × 40 custom layout.',
52+
load: {
53+
mapId: 'my_new_map',
54+
tiledJsonUrl: myNewMapJsonUrl, // import via ?url
55+
arenaBlocksUrl,
56+
gameObjectBlocksUrl,
57+
spawningBlocksUrl
58+
}
59+
}
60+
```
61+
62+
The sidebar picks up the new entry automatically on the next dev server restart.
63+
64+
## Validation
65+
66+
After adding a map, verify the parser output:
67+
68+
```bash
69+
cd environment/react_frontend
70+
npx tsx scripts/dump-parser.mjs
71+
```
72+
73+
This prints zone counts, equipment counts, and wall segment counts for every registered map. Cross-check these against the Tiled editor to confirm the parser decoded the map correctly.

0 commit comments

Comments
 (0)