Add crowd presentation, fix NPC navigation, refresh screenshots
NPC navigation (previously dead in the live game) - Only assign NavigationAgent3D.target_position when it changes: re-assigning every frame invalidated the path, so get_next_path_position() kept returning the current point and avatars never moved. - Follow the first path point that is closer to the destination than the avatars are. Nav points sit on the navmesh plane (y=0) while bodies stand at y~0.5, so the old 3D distance test always looked "already there"; path[0] is also the old start point, so a naive "far enough away" pick steered backwards and oscillated. - Fix fsm.state -> fsm.current_state in the idle-lifetime check. Crowd presentation - Aggressive behavior, visible weapons, and responder arrivals make idle bystanders stop and face the incident for 12 s; someone waiting at the entrance keeps focus. - Panic (mood >= 0.75) walks people away from the door at 2.4 m/s, then they despawn. - Wire reactions to EventBus.crowd_changed: CrowdSystem.apply_event() emits on the bus, not on its own `changed` signal (the first wiring received nothing). - Measured a 16-NPC entrance at ~1.2 ms/frame (824 fps, vsync off), so no LOD/culling. World and HUD - Skyline is now a ring of four alpha-scissored planes at 200 m instead of a single plane; PlaneMesh defaults to horizontal, which is why the backdrop rendered as a band overhead. Larger ground plane, mipmapped texture, scissor threshold 0.5 (0.3 left mip dashes in the sky), lifted albedo so the city is not pure black. - Flashlight aims straight ahead and lights the person/door instead of the ground. - Shorter HUD controls line so it stops overlapping itself. Tests, tooling, docs - Headless suite 98 -> 105 (new test_crowd_presentation.gd, 7 tests). - New smoke_crowd.gd: rubbernecking, panic, and flee-target sanity. - capture_screenshots.gd starts a shift from the venue menu and stages the night set properly; all six README screenshots regenerated. - README, TODO/DONE (crowd section moved), MEMORY updated. Version 0.2.4.
This commit is contained in:
1 parent
f5ad1642b5
commit
71b0b53705
21 files changed
+512
-60
No files matched your search
@@ -21,8 +21,8 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
||||
Windowed control smoke: `godot --path . --script res://src/test/smoke_focus_radial.gd` → exit 0.
|
||||
- `main` is in sync with origin. **v0.2.2** tags the session-shell + world-follow-up commit;
|
||||
CI builds the portable binaries, NSIS installer, and AppImage on the tag push.
|
||||
- Next work: `TODO.md` crowd presentation (rubberneck + panicked NPCs), then emergency aftermath
|
||||
(evacuation, unjustified-call investigation).
|
||||
- Next work: `TODO.md` emergency aftermath (evacuation, unjustified-call investigation), then
|
||||
body-camera replay timeline and NPC theater poses.
|
||||
- Do not re-open `override_access`. Do not bind wand to `W`. Do not remove the radio easter egg.
|
||||
|
||||
## Current State
|
||||
@@ -69,11 +69,38 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
||||
default, `B` toggles it, and a formal warning arms it and writes a `body_camera` incident event.
|
||||
Blocking also calls `_station_between()` to physically place the guard 0.9 m from the NPC toward
|
||||
the entrance. `_request_reason()` logs the NPC's seeded `dialogue_lines` pick.
|
||||
- **NPC navigation was dead (fixed 2026-10-31):** avatars never moved in the live game, for three
|
||||
stacked reasons. (1) `agent.target_position` was re-assigned every physics frame, which
|
||||
invalidates the path so `get_next_path_position()` keeps returning the current point — only set
|
||||
it when the target changes. (2) Nav path points sit on the navmesh plane (y=0) while bodies stand
|
||||
at y≈0.5, so a 3D "are we there" test always looked 0.5 m away and the horizontal direction was
|
||||
zero — compare horizontally. (3) `path[0]` is the old start point, so picking any point "far
|
||||
enough away" can pick the point behind you and oscillate — pick the first point that is
|
||||
**closer to the goal** than the current position. `_nav_try_frames`/`_move_direct` fallback is
|
||||
still there.
|
||||
- **Crowd reactions listen on the EventBus:** `CrowdSystem.apply_event()` emits
|
||||
`event_bus.crowd_changed`; its own `changed` signal is never emitted. `GameManager._connect_crowd()`
|
||||
therefore connects to `coordinator.event_bus.crowd_changed` (reconnect after every
|
||||
`coordinator.setup()`, which recreates both crowd and event bus).
|
||||
- **Crowd presentation:** `NpcAvatar.start_rubberneck(at)` (idle bystanders away from the door stop
|
||||
and face the incident for 12 s) and `start_flee(from)` (panic: walk away at 2.4 m/s, then free
|
||||
themselves). Someone `awaiting_interaction` within 3 m of the door is "engaged" and does not
|
||||
rubberneck. Use `position` (not `global_position`) for that distance check so it also works in
|
||||
headless tests outside the tree.
|
||||
- **Perf:** a busy 16-NPC entrance measured ~1.2 ms/frame (824 fps with vsync off, 800 samples),
|
||||
so LOD/culling is not warranted at `MAX_LIVE_NPCS = 16`. Re-measure before raising the cap.
|
||||
- **Incident recording was dead:** `IncidentRecorder.start_incident()` was never called, so every
|
||||
`add_event`/`add_access_evidence` silently no-opped and the review always showed 0 incidents.
|
||||
`_begin_incident()` now starts one per focused NPC (`has_open_incident()` guard), verbs call
|
||||
`_ensure_incident()`, and `ShiftCoordinator._finish_shift()` closes leftovers as `unresolved`
|
||||
before building the review.
|
||||
- **Skyline backdrop (2026-10-31):** `PlaneMesh` defaults to a **horizontal** XZ plane — set
|
||||
`mesh.orientation = PlaneMesh.FACE_Z` or the "backdrop" renders as a band overhead. The skyline
|
||||
is now a ring of four 400x60 planes at 200 m (center y=30, so building bases meet the ground),
|
||||
`TEXTURE_FILTER` default, `TRANSPARENCY_ALPHA_SCISSOR` with threshold **0.5** (0.3 left faint
|
||||
mip dashes across the sky), unshaded with a lifted albedo (`Color(2.6,2.7,3.0)`) so it is not
|
||||
pure black. Ground is 500x500 so no sky gap shows under the buildings. The `skyline.png` import
|
||||
needs `mipmaps/generate=true`.
|
||||
- **Asset gotcha:** `assets/textures/game_ready/` is *not* imported (no `.import`). Use the
|
||||
imported `assets/textures/skyline.png` path. Untracked duplicate folder should not be referenced.
|
||||
- Host sign-in follow-up remains deferred. PLAN.md + ADRs 0007–0010 still describe the intended UX.
|
||||
|
||||
Reference in new issue
Block a user