diff --git a/CHANGELOG.md b/CHANGELOG.md index 97928c4..bc93823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,33 @@ All notable user-facing changes to Gig Line. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use Semantic Versioning. -## [Unreleased] +## [0.7.0] — 2026-11-01 ### Changed - Agent workflow now requires maintaining this changelog for every patch, minor, and major version. +## [Unreleased] + +### Added + +- Distinct venue geometry: the Joke Shop is now a narrow brick alley with a + shuttered facade, warm wall lantern, dumpster and AC clutter; the TV Studio + has a stone facade, bulb-lit canopy, glossy black car and a stanchion/barrier + line at the door. +- The README gallery includes captured Joke Shop and TV Studio views. + +### Changed + +- Venue geometry is data-driven through `WorldBuilder.VENUE_SPECS`; switching + venues rebuilds the world while keeping the guard and UI intact. +- Each venue has its own navigation bounds and concept-gallery mapping. + +### Fixed + +- The Joke Shop no longer renders the Hollywood skyline or marquee. +- The TV Studio no longer uses the boulevard marquee or palm-lined layout. + ## [0.6.1] — 2026-11-01 ### Added diff --git a/DONE.md b/DONE.md index 5c9516e..32224b5 100644 --- a/DONE.md +++ b/DONE.md @@ -1242,3 +1242,40 @@ live in `tools/blender_assets.py` (`_build_gear()`), the contract in `docs/blend - [x] Consider a second character close-up for a hoodie/winter NPC so headgear is covered too. `--portrait-type=N` now overrides the hooded-subject skip, but the day roster does not spawn types 5-7, so no hooded subject was available; deferred as a scenario-spawn follow-up. + +## Venue geometry and the committed gallery (v0.6.1) + +Completed 2026-11-01. All three venues now build distinct geometry from `WorldBuilder.VENUE_SPECS`; the gallery has Joke Shop and TV Studio views (13 good / 0 bad). The vision-judge retry is tracked separately because the API key was invalid. + +Today all three venues share the Hollywood `WorldBuilder` geometry, so the Joke Shop and studio +concepts are only compared on lighting (noted in `tools/scene_concepts.py` `SHOTS`). They also +have no stage in the committed `docs/screenshots/` gallery, only in `capture_realism.gd`. + +- [x] Extract a geometry spec per venue instead of branching inline: `WorldBuilder.VENUE_SPECS` + now holds facade/stair/street/nav dimensions, window/pilaster rows, guard spawn, feature + flags and prop placements for Hollywood, Joke Shop and TV Studio. Unknown ids fall back to + Hollywood; `test_venue_geometry_specs_are_distinct_and_defaulted` checks the narrow Joke Shop + facade, disabled skyline/marquee, studio bulb-canopy flag and fallback. +- [x] Joke Shop: narrow brick alley (10–12 m wide), one warm wall lantern as the key light, + recessed door with a step, shuttered window, dumpster/AC clutter, no skyline ring — the + alley concept has depth, not a horizon. `_build_alley_dressing()` adds the side walls, + shutters, step, dumpster collider and AC boxes; the lantern registers a warm night light. +- [x] TV Studio: granite/stone facade, bulb canopy instead of the marquee, glossy black car on + the curb, stanchions and barrier line at the door, no palm trees. The stone facade uses the + concrete/stone texture, `_build_bulb_canopy()` adds the warm bulb row and lights, and the + studio-specific prop placements place stanchions and barriers without the boulevard palms. +- [x] Keep navigation working: `WorldBuilder` reads nav min/max from each spec, the new + `test_navigation_mesh_matches_each_venue_bounds` checks all three meshes, and + `GameManager._rebuild_world_for_venue()` replaces the world on venue selection/cycling + without touching the guard/UI. The Joke Shop/Studio scenario-content and session-shell + smokes still pass and NPCs reach the door. +- [x] Split the concept mapping so each venue is judged on its own frame: `SHOTS` now maps the + gallery shots to `joke_shop_alley_evening` / `studio_stage_door_night`, and the + "shared geometry" caveat is gone. `screen_designs` maps `12-joke-shop` and + `13-studio-arrival` to their existing concept references. +- [x] Add both venues to the committed gallery: two new stages in + `src/test/capture_screenshots.gd` (`12-joke-shop.png`, `13-studio-arrival.png`) reuse the + existing `joke_shop_queue` / `studio_arrival` designs. `check_screenshots.sh` is **13 good / + 0 bad**; the new screenshots are 18.2 / 32.6 MAE against their references. +- [-] Update `docs/architecture.md` (venue geometry), `README.md` (gallery) and the screenshot + table when the stages land. diff --git a/MEMORY.md b/MEMORY.md index 6ff9aab..22af419 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -72,7 +72,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o ## Handoff (read this first) -- **Status (2026-11-01, after the gear-fit pass):** v0.6.1 is released. Post-v0.6.0 the +- **Status (2026-11-01, after the venue-geometry pass):** v0.7.0 is released. Post-v0.6.0 the realism pass, the visual-alignment pass and the NPC face/hand pass all landed. Headless **229/229**, Python tooling **87/87**, `tools/check_screenshots.sh` **11 good / 0 bad**, `screen_designs.py compare` 11 good / 0 bad, `scene_concepts.py audit` all GOOD, @@ -246,7 +246,8 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o helmet is lifted 2 cm above the brow. `test_npc_character` guards head/back gear in Godot. Blender's GLB export is byte-nondeterministic for unchanged assets (padding/index order), so do not commit a full no-op rebuild; rebuild only the assets whose geometry changed. -- **Venue geometry (v0.6.1):** `WorldBuilder.VENUE_SPECS` is the per-venue table; `WorldBuilder.new(root, id)` / `set_venue()` select it, unknown ids fall back to `hollywood_building`. Dimensions, window/pilaster rows, stairs, street, nav bounds, guard spawn and feature flags are read from the spec. Joke Shop and TV Studio currently use empty `prop_placements`; bespoke alley/studio dressing and the `GameManager` rebuild-on-venue-switch are follow-ups. +- **Venue geometry (v0.6.1+):** `WorldBuilder.VENUE_SPECS` is the per-venue table; `WorldBuilder.new(root, id)` / `set_venue()` select it, unknown ids fall back to `hollywood_building`. Dimensions, window/pilaster rows, stairs, street, nav bounds, guard spawn, feature flags and prop placements are read from the spec. `GameManager.world_root` holds only the world; `_rebuild_world_for_venue()` frees/recreates it on venue selection/cycling so guard/UI survive. Joke Shop uses procedural alley walls/shutters/dumpster/AC; TV Studio uses a stone facade, bulb canopy, car paint and barrier placements. Gallery shots 12/13 use the existing `joke_shop_queue`/`studio_arrival` references. +- **Screen-design judge (2026-11-01):** `screen_designs.py judge` could not run because `OPENROUTER_API_KEY` returned HTTP 400 "API key not valid". `check_screenshots.sh` still passed 13/13; the judge retry is a TODO follow-up. - **Portrait capture:** `capture_realism.gd` faces the **Rig** toward the camera (using the inverse look direction) and hides the blue `FocusCircle`; the old code used the wrong yaw and left the focus disc over the face. An explicit `--portrait-type=N` now overrides the hooded diff --git a/PLAN.md b/PLAN.md index e4b219c..a061660 100644 --- a/PLAN.md +++ b/PLAN.md @@ -172,7 +172,7 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD ## Versioning and Delivery -`config/version` in `project.godot` is the single version source (currently `0.6.1`). Releases are tagged `v`; the CI pipeline builds and publishes the six artifacts. Version bumps follow [Semantic Versioning](https://semver.org/). See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands. +`config/version` in `project.godot` is the single version source (currently `0.7.0`). Releases are tagged `v`; the CI pipeline builds and publishes the six artifacts. Version bumps follow [Semantic Versioning](https://semver.org/). See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands. ## Design Direction diff --git a/README.md b/README.md index 439fe1b..adba04a 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,11 @@ The central design goal is simple: | ![Late night: street lamps lit, marquee glowing, dark Hollywood Boulevard](docs/screenshots/05-late-night-avenue.png) | ![Night shift: guard flashlight cutting through the dark](docs/screenshots/06-night-flashlight.png) | | ![Wand screening at The Joke Shop: false alarm on keys](docs/screenshots/07-wand-screening.png) | ![Escalation HUD at L3 with body camera armed and reaction log](docs/screenshots/08-escalation-hud.png) | | ![Body-camera timeline: formal warning, body camera on, blocking](docs/screenshots/09-body-camera-timeline.png) | ![Prop gallery: the six generated street-furniture models on a studio backdrop](docs/screenshots/10-prop-gallery.png) | -| ![Precision walk at the venue steps with the HUD chip visible](docs/screenshots/11-precision-walk.png) | | +| ![Precision walk at the venue steps with the HUD chip visible](docs/screenshots/11-precision-walk.png) | ![The Joke Shop: a narrow brick alley with shuttered windows, a warm wall lantern and a dumpster](docs/screenshots/12-joke-shop.png) | +| ![Late-night TV studio arrival: stone facade, bulb canopy, black car and barriers](docs/screenshots/13-studio-arrival.png) | | -*Screenshots are generated by [`src/test/capture_screenshots.gd`](src/test/capture_screenshots.gd) and [`src/test/capture_prop_gallery.gd`](src/test/capture_prop_gallery.gd) from the current build — day, late-night, wand screening, escalation, shift-review timeline, precision walk, and the prop lineup.* +*Screenshots are generated by [`src/test/capture_screenshots.gd`](src/test/capture_screenshots.gd) and [`src/test/capture_prop_gallery.gd`](src/test/capture_prop_gallery.gd) from the current build — day, late-night, wand screening, escalation, shift-review timeline, +precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup.* ## ⬇️ Downloads @@ -35,9 +37,9 @@ The central design goal is simple: > Downloads are public (no sign-in required) since the host's instance-wide > `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31. > -> **Latest: v0.6.1** — fitted NPC gear, a refreshed portrait/gallery capture and the -> post-v0.6.0 realism/visual-alignment work (v2 skylines/textures, UI mockup art, -> flashlight beam, HUD fixes). +> **Latest: v0.7.0** — distinct Joke Shop and TV Studio geometry, fitted NPC gear, +> and the post-v0.6.0 realism/visual-alignment work (v2 skylines/textures, UI mockup +> art, flashlight beam, HUD fixes). > Releases are built automatically when a `vX.Y.Z` tag is pushed > (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use > `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline @@ -116,9 +118,9 @@ Full setup, every run/test/smoke command, and the controls table are in ## Development Status -v0.6.1 shipped (fitted NPC gear and refreshed captures) on top of v0.6.0's -Blender character/gear/prop library, baked AO and environment lift. The -realism and visual-alignment passes are part of the same release. See +v0.7.0 shipped distinct venue geometry: the Joke Shop alley and TV Studio +entrance no longer share the Hollywood layout. It builds on v0.6.1's fitted NPC +gear and v0.6.0's Blender asset library, baked AO and environment lift. See [`docs/development-status.md`](docs/development-status.md) for the full implemented list and [`TODO.md`](TODO.md) for what's next. Manual verification uses the [`docs/smoke-checklist.md`](docs/smoke-checklist.md). diff --git a/TODO.md b/TODO.md index e084551..b5b8398 100644 --- a/TODO.md +++ b/TODO.md @@ -3,35 +3,7 @@ Work is grouped by theme. Each item should be small enough to complete and verify on its own; see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these plans. -## Venue geometry and the committed gallery (v0.6.1) - -Today all three venues share the Hollywood `WorldBuilder` geometry, so the Joke Shop and studio -concepts are only compared on lighting (noted in `tools/scene_concepts.py` `SHOTS`). They also -have no stage in the committed `docs/screenshots/` gallery, only in `capture_realism.gd`. - -- [x] Extract a geometry spec per venue instead of branching inline: `WorldBuilder.VENUE_SPECS` - now holds facade/stair/street/nav dimensions, window/pilaster rows, guard spawn, feature - flags and prop placements for Hollywood, Joke Shop and TV Studio. Unknown ids fall back to - Hollywood; `test_venue_geometry_specs_are_distinct_and_defaulted` checks the narrow Joke Shop - facade, disabled skyline/marquee, studio bulb-canopy flag and fallback. -- [ ] Joke Shop: narrow brick alley (10–12 m wide), one warm wall lantern as the key light, - recessed door with a step, shuttered window, dumpster/AC clutter, no skyline ring — the - alley concept has depth, not a horizon. -- [ ] TV Studio: granite/stone facade, bulb canopy instead of the marquee, glossy black car on - the curb, stanchions and barrier line at the door, no palm trees. -- [ ] Keep navigation working: regenerate/verify the `NavigationRegion3D` bake per venue, - update sidewalk/steps/door waypoints (Joke Shop and Studio already have `sidewalk` and - `steps_end`), and confirm NPCs still reach the door (smoke + `test_venue_*`). -- [ ] Split the concept mapping so each venue is judged on its own frame (`joke_shop_*`, - `studio_*`) and drop the "shared geometry" caveat in `tools/scene_concepts.py`. -- [ ] Add both venues to the committed gallery: two new stages in - `src/test/capture_screenshots.gd` (`12-joke-shop.png`, `13-studio-arrival.png`) reusing the - existing `joke_shop_queue` / `studio_arrival` designs, then `check_screenshots.sh` and a - fresh judge run. -- [ ] Update `docs/architecture.md` (venue geometry), `README.md` (gallery) and the screenshot - table when the stages land. - -## Forward+ desktop preset and a volumetric flashlight (v0.7.0) +## Forward+ desktop preset and a volumetric flashlight (v0.7.1) The Compatibility renderer has no volumetrics, so the flashlight is a tight pool of light with no visible shaft. A second desktop build can render it properly without touching the CI/default @@ -54,6 +26,8 @@ path. ## Realism follow-ups (deferred) +- [ ] Re-run the screen-design judge for `12-joke-shop` / `13-studio-arrival` when + `OPENROUTER_API_KEY` is valid; the 2026-11-01 attempt failed with an invalid-key HTTP 400. - [ ] Capture a hooded/winter NPC close-up once a scenario spawns types 5-7 (the explicit `--portrait-type` override is already in `capture_realism.gd`). - [ ] Human-authored tree/hedge silhouettes: the whole scene now reads well except the foliage, diff --git a/docs/architecture.md b/docs/architecture.md index 80c6e66..a4cfa91 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -37,7 +37,7 @@ This document follows the structure of the arc42 architecture documentation temp - **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible. - **PATCH** — bug fixes and small polish that do not change behavior. -The single source of truth for the version is `config/version` in `project.godot` (currently `0.6.1`). Each release is tagged in git as `v` (e.g. `v0.6.0`). The `0.x` series is pre-1.0, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md). +The single source of truth for the version is `config/version` in `project.godot` (currently `0.7.0`). Each release is tagged in git as `v` (e.g. `v0.6.0`). The `0.x` series is pre-1.0, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md). ## 3. Context and Scope @@ -78,7 +78,7 @@ The player interacts with NPCs, venue entrances, access artifacts, equipment, st - `CrowdSystem` models queues, attention, congestion, and public reaction. - `ShiftReview` presents decisions, consequences, and improvement opportunities. - `SurfaceMaterials` (`src/game/materials.gd`) holds the surface profiles, textured albedo/normal recipes, baked-AO wiring, glass/emissive recipes, and the environment tune-up (tonemapping, glow, fog, grading) that the phase lighting applies. -- `WorldBuilder` assembles the procedural venue, prefers the Blender prop library in `assets/models/blender/props/`, and falls back to `assets/models/generated/` or primitives. It also wires the phase-driven library emissives and street dressing. +- `WorldBuilder` assembles the procedural venue from a per-venue `VENUE_SPECS` table (facade, windows, stairs, street, nav bounds, guard spawn, feature flags and prop placements), prefers the Blender prop library in `assets/models/blender/props/`, and falls back to `assets/models/generated/` or primitives. `GameManager` rebuilds only the `World` container when the venue changes, so the guard/UI and their state survive. It also wires the phase-driven library emissives and street dressing. - `src/game/ui/art.gd` loads runtime UI/scene art from `assets/images/ui/` and `assets/images/scenes/`, with null fallbacks so panels keep their procedural layouts. - `tools/model_pipeline.py` builds and audits the street-prop `.glb` models and packs their colour atlases. - `tools/npc_pipeline.py` builds and audits the skinned NPC base model (`assets/models/generated/npc_base.glb`) and its per-slot material maps, with an optional AI fabric override (ADR-0013). diff --git a/docs/development-status.md b/docs/development-status.md index b834416..bf01b99 100644 --- a/docs/development-status.md +++ b/docs/development-status.md @@ -5,7 +5,7 @@ Implemented so far: - Godot 4.3 project scaffold, input map, procedural world builder - Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem` - Three authored scenarios (Hollywood Boulevard building, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools -- Headless unit tests: **229 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings) +- Headless unit tests: **231 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings) - Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event - First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review - Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`) @@ -41,6 +41,7 @@ Implemented so far: - Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. - Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a visible additive flashlight beam, and refreshed staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts. - Input/reliability fixes: selecting a venue now releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. +- Venue geometry (v0.7.0): the Joke Shop now builds as a narrow brick alley with a warm wall lantern, shutters, dumpster/AC clutter and no skyline; the TV Studio builds a stone facade, bulb canopy, glossy black car and barrier line. `WorldBuilder.VENUE_SPECS` drives facade/street/nav dimensions, and `GameManager` rebuilds only the world container when the venue changes. - Gear fit (post-v0.6.0): Blender gear is measured against the avatar head/skirt silhouette and the checks run in both the Python tooling and the headless suite. Sunglasses, the shoulder bag and the firefighter helmet were fitted to the new faces; the Influencer portrait and gallery were recaptured with a before/after report. Not yet implemented: a desktop-only Forward+ preset (the renderer stays GL Compatibility for Web parity), live 3D body-camera replay, editor-based GUT integration, and crowd performance profiling/LOD (see [`TODO.md`](../TODO.md)). diff --git a/docs/gameplay.md b/docs/gameplay.md index 136dcb5..9ce3a01 100644 --- a/docs/gameplay.md +++ b/docs/gameplay.md @@ -26,11 +26,11 @@ Authorized people must either be recognized by the guard or present a valid wris ### The Joke Shop -The Joke Shop is a comedy-club scenario. The door person checks IDs, tickets, reservations, and guest-list status, then uses a handheld security wand according to venue policy. The player must interpret wand alerts rather than treating every alert as proof of a weapon; keys, belts, phones, and other harmless objects can trigger false alarms. +The Joke Shop is a narrow brick alley with a recessed door, shuttered windows, a warm wall lantern, a dumpster and wall-mounted AC clutter. The door person checks IDs, tickets, reservations, and guest-list status, then uses a handheld security wand according to venue policy. The player must interpret wand alerts rather than treating every alert as proof of a weapon; keys, belts, phones, and other harmless objects can trigger false alarms. ### Hollywood Boulevard Late Night TV Studio -This scenario combines the previously defined NPC types with a busy television-studio entrance. The player manages audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, street performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people experiencing a mental-health crisis, and rare violent threats. +This scenario combines the previously defined NPC types with a busy television-studio entrance: a stone facade, a bulb-lit canopy, a glossy black car at the curb, and stanchions/barriers holding the arrival line. The player manages audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, street performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people experiencing a mental-health crisis, and rare violent threats. The studio uses wristbands, credentials, tickets, guest-list confirmation, and personal recognition to authorize entry. Crowd control, filming boundaries, following authorized guests through the door, and counterfeit credentials are key events. diff --git a/docs/screenshots/01-venue-overview.png b/docs/screenshots/01-venue-overview.png index 2b8ead8..98d52d4 100644 Binary files a/docs/screenshots/01-venue-overview.png and b/docs/screenshots/01-venue-overview.png differ diff --git a/docs/screenshots/02-targeting-npc.png b/docs/screenshots/02-targeting-npc.png index 03db192..63d80e1 100644 Binary files a/docs/screenshots/02-targeting-npc.png and b/docs/screenshots/02-targeting-npc.png differ diff --git a/docs/screenshots/05-late-night-avenue.png b/docs/screenshots/05-late-night-avenue.png index f607bdf..dbdc3cf 100644 Binary files a/docs/screenshots/05-late-night-avenue.png and b/docs/screenshots/05-late-night-avenue.png differ diff --git a/docs/screenshots/06-night-flashlight.png b/docs/screenshots/06-night-flashlight.png index 8c2cd4a..ac9469d 100644 Binary files a/docs/screenshots/06-night-flashlight.png and b/docs/screenshots/06-night-flashlight.png differ diff --git a/docs/screenshots/08-escalation-hud.png b/docs/screenshots/08-escalation-hud.png index 8d58651..46b3ee4 100644 Binary files a/docs/screenshots/08-escalation-hud.png and b/docs/screenshots/08-escalation-hud.png differ diff --git a/docs/screenshots/09-body-camera-timeline.png b/docs/screenshots/09-body-camera-timeline.png index cc2625c..d9ffb33 100644 Binary files a/docs/screenshots/09-body-camera-timeline.png and b/docs/screenshots/09-body-camera-timeline.png differ diff --git a/docs/screenshots/11-precision-walk.png b/docs/screenshots/11-precision-walk.png index fc285a1..599a091 100644 Binary files a/docs/screenshots/11-precision-walk.png and b/docs/screenshots/11-precision-walk.png differ diff --git a/docs/screenshots/12-joke-shop.png b/docs/screenshots/12-joke-shop.png new file mode 100644 index 0000000..68d23ff Binary files /dev/null and b/docs/screenshots/12-joke-shop.png differ diff --git a/docs/screenshots/13-studio-arrival.png b/docs/screenshots/13-studio-arrival.png new file mode 100644 index 0000000..e767906 Binary files /dev/null and b/docs/screenshots/13-studio-arrival.png differ diff --git a/project.godot b/project.godot index 391d3ba..f0b1ed9 100644 --- a/project.godot +++ b/project.godot @@ -12,7 +12,7 @@ config_version=5 config/name="Gig Line" config/description="Workplace security simulation about observation, access verification, and proportionate escalation." -config/version="0.6.1" +config/version="0.7.0" run/main_scene="res://scenes/main.tscn" config/features=PackedStringArray("4.3", "GL Compatibility") diff --git a/src/game/game_manager.gd b/src/game/game_manager.gd index 6045ed1..1b4951e 100644 --- a/src/game/game_manager.gd +++ b/src/game/game_manager.gd @@ -32,6 +32,9 @@ const SettingsPanel := preload("res://src/game/ui/settings_panel.gd") const EventTimeline := preload("res://src/game/ui/event_timeline.gd") var world_builder: WorldBuilder = null +## Dedicated container for the procedural world, so switching venues can rebuild +## the geometry without touching the guard, UI, or other scene-root children. +var world_root: Node3D = null var door := DoorState.new() var flashlight_on := false var body_camera_on := false @@ -135,8 +138,11 @@ func _ready() -> void: scene_root = Node3D.new() scene_root.name = "SceneRoot" add_child(scene_root) + world_root = Node3D.new() + world_root.name = "World" + scene_root.add_child(world_root) - world_builder = WorldBuilder.new(scene_root) + world_builder = WorldBuilder.new(world_root, active_scenario_id) world_builder.build() _ensure_lighting() @@ -235,9 +241,26 @@ func _build_scenario(id: String) -> VenueScenario: func switch_scenario(id: String) -> void: active_scenario_id = id selected_scenario = _build_scenario(id) + _rebuild_world_for_venue(id) if coordinator: restart_shift() +## Rebuild the procedural venue in place. The guard/UI stay under `SceneRoot`; +## only the `World` container is replaced, so navigation, props, lights and the +## venue-specific dressing match the selected scenario. +func _rebuild_world_for_venue(id: String) -> void: + if world_root == null: + return + for child in world_root.get_children(): + child.free() + world_builder = WorldBuilder.new(world_root, id) + world_builder.build() + if guard != null: + guard.global_position = world_builder.spec.get("guard_spawn", Vector3(0, 0.1, -2.0)) + guard.rotation.y = PI + if world_builder != null: + world_builder.set_door_locked(door.locked) + func restart_shift() -> void: if shift_review_panel: shift_review_panel.hide() @@ -301,6 +324,7 @@ func _on_venue_selected(scenario_id: String) -> void: active_scenario_id = scenario_id _venue_index = maxi(0, VENUE_ORDER.find(scenario_id)) selected_scenario = _build_scenario(scenario_id) + _rebuild_world_for_venue(scenario_id) if hud: hud.set_wand_icon(false) _reset_shift_state() diff --git a/src/game/world_builder.gd b/src/game/world_builder.gd index d81204a..535da0e 100644 --- a/src/game/world_builder.gd +++ b/src/game/world_builder.gd @@ -68,7 +68,8 @@ const VENUE_SPECS := { "hollywood_building": { "display_name": "Hollywood Boulevard Building", "facade_width": 40.0, "facade_height": 18.0, "facade_depth": 4.0, - "facade_center_z": -6.0, + "facade_center_z": -6.0, "facade_texture": T_BRICK, + "facade_tint": Color(0.86, 0.85, 0.83), "facade_role": "brick", "window_columns": [-17.0, -13.6, -10.2, -6.8, 6.8, 10.2, 13.6, 17.0], "pilaster_columns": [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3], "window_rows": [9.2, 12.4], @@ -80,13 +81,14 @@ const VENUE_SPECS := { "features": { "marquee": true, "skyline": true, "handrail": true, "sedan": true, "wall_lanterns": true, "palms": true, "street_trees": true, - "crosswalk": true, "bulb_canopy": false, + "street_lamps": true, "crosswalk": true, "bulb_canopy": false, }, }, "joke_shop": { "display_name": "The Joke Shop", "facade_width": 12.0, "facade_height": 12.0, "facade_depth": 5.0, - "facade_center_z": -6.5, + "facade_center_z": -6.5, "facade_texture": T_BRICK, + "facade_tint": Color(0.78, 0.66, 0.58), "facade_role": "brick", "window_columns": [-3.8, 3.8], "pilaster_columns": [-5.2, 5.2], "window_rows": [8.2], @@ -98,14 +100,15 @@ const VENUE_SPECS := { "features": { "marquee": false, "skyline": false, "handrail": false, "sedan": false, "wall_lanterns": true, "palms": false, "street_trees": false, - "crosswalk": false, "bulb_canopy": false, + "street_lamps": false, "crosswalk": false, "bulb_canopy": false, }, "prop_placements": [], }, "tv_studio": { "display_name": "Late Night TV Studio", "facade_width": 30.0, "facade_height": 15.0, "facade_depth": 4.0, - "facade_center_z": -6.0, + "facade_center_z": -6.0, "facade_texture": T_CONCRETE, + "facade_tint": Color(0.58, 0.57, 0.55), "facade_role": "stone", "window_columns": [-11.0, -7.2, 7.2, 11.0], "pilaster_columns": [-13.2, -9.0, -5.0, 5.0, 9.0, 13.2], "window_rows": [10.5], @@ -114,12 +117,19 @@ const VENUE_SPECS := { "street_width": 100.0, "street_depth": 24.0, "nav_min": Vector2(-14.0, -0.6), "nav_max": Vector2(14.0, 18.0), "guard_spawn": Vector3(0.0, 0.1, -1.8), + "sedan_position": Vector3(5.4, 0.1, 8.8), "sedan_rotation": 90.0, "features": { "marquee": false, "skyline": true, "handrail": true, "sedan": true, "wall_lanterns": false, "palms": false, "street_trees": true, - "crosswalk": true, "bulb_canopy": true, + "street_lamps": true, "crosswalk": true, "bulb_canopy": true, }, - "prop_placements": [], + "prop_placements": [ + {"zone": "entrance", "prop": "stanchion", "x": -2.8, "z": 4.4}, + {"zone": "entrance", "prop": "stanchion", "x": 2.8, "z": 4.4}, + {"zone": "entrance", "prop": "crowd_barrier", "x": -5.2, "z": 4.2}, + {"zone": "entrance", "prop": "crowd_barrier", "x": 5.2, "z": 4.2}, + {"zone": "curb", "prop": "security_podium", "x": 4.6, "z": 5.9, "rot": -15.0}, + ], }, } const DEFAULT_VENUE := "hollywood_building" @@ -147,6 +157,8 @@ func build() -> void: _build_skyline_backdrop() if _feature("marquee"): _build_marquee() + if _feature("bulb_canopy"): + _build_bulb_canopy() _build_steps() _build_sidewalk() _build_street() @@ -155,6 +167,8 @@ func build() -> void: _build_street_furniture() _build_street_detail() _build_library_dressing() + if venue_id == "joke_shop": + _build_alley_dressing() _build_guard_spawn() _build_navmesh() set_night_mode(false) @@ -197,8 +211,11 @@ func _build_building() -> void: var d := float(spec["facade_depth"]) var cz := float(spec["facade_center_z"]) var front := cz + d * 0.5 - # Main facade (textured brick) - _add_box(building, Vector3(w, h, d), Vector3(0, h * 0.5, cz), Color(0.86, 0.85, 0.83), false, T_BRICK, Vector2(w * 0.55, h * 0.55), "brick") + # Main facade (brick on the boulevard/alley, stone on the studio) + var facade_texture: Texture2D = spec.get("facade_texture", T_BRICK) + var facade_tint: Color = spec.get("facade_tint", Color(0.86, 0.85, 0.83)) + var facade_role := str(spec.get("facade_role", "brick")) + _add_box(building, Vector3(w, h, d), Vector3(0, h * 0.5, cz), facade_tint, false, facade_texture, Vector2(w * 0.55, h * 0.55), facade_role) # Stone base course so the facade has a footing instead of meeting the steps dead-on. _add_box(building, Vector3(w, 0.8, 0.5), Vector3(0, 0.4, front + 0.22), Color(0.52, 0.5, 0.47), false, null, Vector2.ONE, "stone") # Facade depth, not a flat wall: a projecting plinth, a string course between @@ -209,7 +226,7 @@ func _build_building() -> void: _add_box(building, Vector3(w, 0.22, 0.2), Vector3(0, 10.9, front + 0.1), Color(0.58, 0.56, 0.53), true, null, Vector2.ONE, "stone") for bay in spec.get("pilaster_columns", [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3]): _add_box(building, Vector3(0.5, 8.4, 0.24), Vector3(float(bay), 12.6, front + 0.1), - Color(0.8, 0.78, 0.76), true, T_BRICK, Vector2(0.3, 4.7), "brick") + Color(0.8, 0.78, 0.76), true, facade_texture, Vector2(0.3, 4.7), facade_role) # Entrance recess + door recess frame _add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, front + 0.1), Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "stone") # Door frame columns and lintel, so the entrance is framed rather than painted on. @@ -486,26 +503,143 @@ func _build_library_dressing() -> void: library_nodes.append(rail) _apply_library_ao(rail, "handrail") if _feature("sedan") and ResourceLoader.exists(BLENDER_PROP_DIR + "sedan.glb"): - var sedan := _add_prop_model(root, "sedan", Vector3(6.0, 0.1, 17.0), 180.0) + var sedan_pos: Vector3 = spec.get("sedan_position", Vector3(6.0, 0.1, 17.0)) + var sedan_rot := float(spec.get("sedan_rotation", 180.0)) + var sedan := _add_prop_model(root, "sedan", sedan_pos, sedan_rot) sedan.name = "ParkedSedan" + if venue_id == "tv_studio": + _apply_car_paint(sedan) if not library_nodes.has(sedan): library_nodes.append(sedan) for material in _emissive_materials(sedan): if not _lamp_bulb_mats.has(material): _lamp_bulb_mats.append(material) if _feature("wall_lanterns") and ResourceLoader.exists(BLENDER_PROP_DIR + "wall_lantern.glb"): - for side in [-4.9, 4.9]: - var lantern := _load_prop_scene("wall_lantern") - if lantern == null: - continue - lantern.name = "WallLantern" - lantern.position = Vector3(side, 3.3, -3.78) - root.add_child(lantern) - library_nodes.append(lantern) - _apply_library_ao(lantern, "wall_lantern") - for material in _emissive_materials(lantern): - if not _lamp_bulb_mats.has(material): - _lamp_bulb_mats.append(material) + if venue_id == "joke_shop": + _add_wall_lantern(Vector3(3.2, 3.3, -3.72), true) + else: + for side in [-4.9, 4.9]: + _add_wall_lantern(Vector3(side, 3.3, -3.78), false) + +## The TV studio entrance canopy: a dark metal slab with a row of warm bulbs, +## replacing the boulevard marquee. +func _build_bulb_canopy() -> void: + var front := float(spec["facade_center_z"]) + float(spec["facade_depth"]) * 0.5 + var canopy := _add_box(root, Vector3(7.2, 0.22, 2.2), + Vector3(0, 5.7, front + 1.0), Color(0.16, 0.16, 0.18), + true, null, Vector2.ONE, "metal") + canopy.name = "BulbCanopy" + for x in [-3.0, 3.0]: + _add_box(root, Vector3(0.12, 5.5, 0.12), + Vector3(x, 2.75, front + 1.95), Color(0.20, 0.20, 0.22), + true, null, Vector2.ONE, "metal") + for x in [-2.8, -1.4, 0.0, 1.4, 2.8]: + var bulb := MeshInstance3D.new() + var sphere := SphereMesh.new() + sphere.radius = 0.075 + sphere.height = 0.15 + bulb.mesh = sphere + var mat := _emissive_material(Color(1.0, 0.78, 0.42)) + bulb.material_override = mat + bulb.position = Vector3(x, 5.53, front + 1.0) + root.add_child(bulb) + if not _marquee_emissive.has(mat): + _marquee_emissive.append(mat) + for x in [-2.2, 2.2]: + var omni := OmniLight3D.new() + omni.position = Vector3(x, 5.15, front + 1.4) + omni.omni_range = 8.0 + omni.light_energy = 0.0 + omni.light_color = Color(1.0, 0.72, 0.36) + root.add_child(omni) + _lamp_omni.append(omni) + +## Glossy black paint for the studio's parked car. The library sedan keeps its +## tintable slots; the studio overrides the body meshes with a single paint. +func _apply_car_paint(holder: Node3D) -> void: + var paint := StandardMaterial3D.new() + paint.resource_name = "StudioCarPaint" + paint.albedo_color = Color(0.018, 0.020, 0.024) + paint.metallic = 0.85 + paint.roughness = 0.12 + paint.metallic_specular = 0.8 + for node in holder.find_children("*", "MeshInstance3D", true, false): + var mi := node as MeshInstance3D + if mi != null: + mi.material_override = paint + +## One wall lantern plus an optional warm light. The Joke Shop uses a single +## lantern as its key light; the boulevard keeps two unlit visual lanterns. +func _add_wall_lantern(pos: Vector3, with_light: bool) -> void: + var lantern := _load_prop_scene("wall_lantern") + if lantern == null: + return + lantern.name = "WallLantern" + lantern.position = pos + root.add_child(lantern) + library_nodes.append(lantern) + _apply_library_ao(lantern, "wall_lantern") + for material in _emissive_materials(lantern): + if not _lamp_bulb_mats.has(material): + _lamp_bulb_mats.append(material) + if with_light: + var omni := OmniLight3D.new() + omni.name = "LanternGlow" + omni.position = pos + Vector3(0.0, -0.1, 0.45) + omni.omni_range = 9.0 + omni.light_energy = 0.0 + omni.light_color = Color(1.0, 0.72, 0.38) + root.add_child(omni) + _lamp_omni.append(omni) + +## The Joke Shop alley: two brick side walls frame a 12 m passage, with +## shutters, a recessed door step, a dumpster and wall-mounted AC boxes. +func _build_alley_dressing() -> void: + var front := float(spec["facade_center_z"]) + float(spec["facade_depth"]) * 0.5 + var height := float(spec["facade_height"]) + var wall_x := float(spec["facade_width"]) * 0.5 + 0.35 + for side in [-1.0, 1.0]: + var wall := _add_box(root, Vector3(0.7, height, 15.0), + Vector3(side * wall_x, height * 0.5, front + 7.5), Color(0.72, 0.70, 0.68), + true, T_BRICK, Vector2(8, 6), "brick") + wall.name = "AlleyWall" + _add_box(root, Vector3(0.9, 0.35, 15.0), + Vector3(side * wall_x, 0.18, front + 7.5), Color(0.44, 0.42, 0.40), + true, null, Vector2.ONE, "stone") + # Shutters over the two upper windows. + for x in [-3.8, 3.8]: + _add_box(root, Vector3(2.0, 2.5, 0.12), Vector3(x, 8.2, front + 0.22), + Color(0.18, 0.24, 0.21), true, null, Vector2.ONE, "painted_metal") + for slat in range(-4, 5): + _add_box(root, Vector3(1.9, 0.07, 0.04), + Vector3(x, 8.2 + slat * 0.28, front + 0.30), + Color(0.24, 0.31, 0.27), false, null, Vector2.ONE, "painted_metal") + # The recessed door gets a low stone step into the alley. + _add_box(root, Vector3(3.4, 0.16, 0.55), Vector3(0, 0.08, front + 0.75), + Color(0.48, 0.45, 0.42), true, null, Vector2.ONE, "stone") + # Dumpster and its lid; a solid body so guests and the guard walk around it. + var dump := StaticBody3D.new() + dump.name = "AlleyDumpster" + dump.position = Vector3(-4.2, 0, front + 9.2) + _add_box(dump, Vector3(1.9, 1.15, 1.25), Vector3(0, 0.58, 0), + Color(0.16, 0.28, 0.21), true, null, Vector2.ONE, "painted_metal") + _add_box(dump, Vector3(2.0, 0.08, 1.35), Vector3(0, 1.18, -0.02), + Color(0.11, 0.19, 0.15), true, null, Vector2.ONE, "painted_metal") + var dump_col := BoxShape3D.new() + dump_col.size = Vector3(1.9, 1.2, 1.3) + var owner_id := dump.create_shape_owner(dump) + dump.shape_owner_add_shape(owner_id, dump_col) + dump.shape_owner_set_transform(owner_id, Transform3D(Basis(), Vector3(0, 0.6, 0))) + root.add_child(dump) + # AC boxes on the inside faces of the walls, above head height. + for side in [-1.0, 1.0]: + for z in [front + 3.0, front + 7.0]: + _add_box(root, Vector3(0.55, 0.8, 1.1), + Vector3(side * (wall_x - 0.5), 5.8, z), + Color(0.56, 0.57, 0.55), true, null, Vector2.ONE, "metal") + _add_box(root, Vector3(0.08, 0.55, 0.8), + Vector3(side * (wall_x - 0.78), 5.8, z), + Color(0.22, 0.23, 0.22), false, null, Vector2.ONE, "metal") ## Emissive materials under a prop, used to tie library bulbs/LEDs to night mode. func _emissive_materials(holder: Node3D) -> Array[StandardMaterial3D]: @@ -525,18 +659,19 @@ func _build_street_furniture() -> void: # Blender library street lamps (bulbs + omni lights, off by day). The lamp # arm points to local -X, so the left side is rotated 180 degrees to face # the sidewalk. - for side in [-7.5, 7.5]: - var holder := _add_prop_model(root, "street_lamp", - Vector3(side, 0.18, 8.5), 180.0 if side < 0.0 else 0.0) - holder.name = "Lamp_%d" % int(side) - var omni := OmniLight3D.new() - omni.position = Vector3(-0.85, 4.65, 0) - omni.omni_range = 12.0 - omni.light_energy = 0.0 - omni.light_color = Color(1, 0.93, 0.75) - holder.add_child(omni) - _lamp_omni.append(omni) - _collect_emissive(holder) + if _feature("street_lamps"): + for side in [-7.5, 7.5]: + var holder := _add_prop_model(root, "street_lamp", + Vector3(side, 0.18, 8.5), 180.0 if side < 0.0 else 0.0) + holder.name = "Lamp_%d" % int(side) + var omni := OmniLight3D.new() + omni.position = Vector3(-0.85, 4.65, 0) + omni.omni_range = 12.0 + omni.light_energy = 0.0 + omni.light_color = Color(1, 0.93, 0.75) + holder.add_child(omni) + _lamp_omni.append(omni) + _collect_emissive(holder) # Blender library street trees replace the cone trees. Flank the walkway and # keep the sight line to the door clear. if _feature("street_trees"): diff --git a/src/test/capture_screenshots.gd b/src/test/capture_screenshots.gd index 1cdd1f5..05363ac 100644 --- a/src/test/capture_screenshots.gd +++ b/src/test/capture_screenshots.gd @@ -207,6 +207,36 @@ func _advance() -> void: _shoot("11-precision-walk.png") Input.action_release("move_forward") Input.action_release("walk_slow") + if gm != null: + gm.switch_scenario("joke_shop") + gm.coordinator.elapsed_s = 620.0 + state = 12 + state_frames = 0 + 12: + if state_frames == 1 and gm != null: + gm.guard.global_position = Vector3(-1.5, 0.1, 8.5) + gm.guard.rotation.y = deg_to_rad(-5.0) + gm.guard.head.rotation.x = deg_to_rad(2.0) + gm.hud.clear_log() + gm.hud.set_escalation(0) + gm.hud.set_body_camera(false) + if state_frames >= 90: + _shoot("12-joke-shop.png") + if gm != null: + gm.switch_scenario("tv_studio") + gm.coordinator.elapsed_s = 620.0 + state = 13 + state_frames = 0 + 13: + if state_frames == 1 and gm != null: + gm.guard.global_position = Vector3(0, 0.1, 14.0) + gm.guard.rotation.y = 0.0 + gm.guard.head.rotation.x = deg_to_rad(2.0) + gm.hud.clear_log() + gm.hud.set_escalation(0) + gm.hud.set_body_camera(false) + if state_frames >= 120: + _shoot("13-studio-arrival.png") quit(0) ## Put the guard a couple of steps in front of the target so a staged UI capture diff --git a/src/test/tests/test_world.gd b/src/test/tests/test_world.gd index 70b8b4d..bd4bf8f 100644 --- a/src/test/tests/test_world.gd +++ b/src/test/tests/test_world.gd @@ -52,6 +52,38 @@ func test_venue_geometry_specs_are_distinct_and_defaulted() -> bool: root.free() return a and b and c and d and e and f and g +func test_navigation_mesh_matches_each_venue_bounds() -> bool: + var problems: Array[String] = [] + for id in ["hollywood_building", "joke_shop", "tv_studio"]: + var root := Node3D.new() + var builder := WorldBuilder.new(root, id) + builder.build() + var region := root.find_child("NavRegion", true, false) as NavigationRegion3D + if region == null or region.navigation_mesh == null: + problems.append("%s has no navmesh" % id) + root.free() + continue + var verts := region.navigation_mesh.get_vertices() + var nav_min: Vector2 = builder.spec["nav_min"] + var nav_max: Vector2 = builder.spec["nav_max"] + var min_x := 999.0 + var max_x := -999.0 + var min_z := 999.0 + var max_z := -999.0 + for v in verts: + min_x = minf(min_x, v.x) + max_x = maxf(max_x, v.x) + min_z = minf(min_z, v.z) + max_z = maxf(max_z, v.z) + if absf(min_x - nav_min.x) > 0.01 or absf(max_x - nav_max.x) > 0.01 \ + or absf(min_z - nav_min.y) > 0.01 or absf(max_z - nav_max.y) > 0.01: + problems.append("%s navmesh %s does not match spec %s" % [ + id, str(Vector4(min_x, max_x, min_z, max_z)), + str(Vector4(nav_min.x, nav_max.x, nav_min.y, nav_max.y))]) + root.free() + return TestUtils.check(problems.is_empty(), + "each venue builds its own navigation bounds (%s)" % str(problems)) + func test_phase_lighting_config_covered() -> bool: var ok := true for phase in [Rules.Phase.EARLY, Rules.Phase.MIDDAY, Rules.Phase.EVENING, Rules.Phase.LATE]: diff --git a/tools/scene_concepts.py b/tools/scene_concepts.py index 05274ff..31fc746 100644 --- a/tools/scene_concepts.py +++ b/tools/scene_concepts.py @@ -191,10 +191,13 @@ SHOTS = { "street_night": "boulevard_skyline_dusk", "entrance_late": "hollywood_entrance_rain_night", "street_late": "boulevard_skyline_dusk", - # The venues share the Hollywood geometry for now, so these two compare - # lighting and atmosphere rather than layout (noted in TODO.md). + # Each venue is judged on its own frame; the real geometry no longer shares + # the Hollywood layout. "joke_shop_door": "joke_shop_door_closeup", + "12-joke-shop": "joke_shop_alley_evening", "studio_door": "studio_stage_door_night", + "studio_stage_door_night": "studio_stage_door_night", + "13-studio-arrival": "studio_stage_door_night", "npc_portrait": "npc_portrait_day", } diff --git a/tools/screen_designs.py b/tools/screen_designs.py index 34ecca3..66fd409 100644 --- a/tools/screen_designs.py +++ b/tools/screen_designs.py @@ -369,7 +369,7 @@ SCREENS = [ "kind": "scene", "title": "Joke Shop door queue", "source": "PLAN.md The Joke Shop — long line, ID and ticket checks", - "ref": None, + "ref": "12-joke-shop.png", "description": "Evening view of a small comedy-club entrance: a short queue " "on the sidewalk, a door person checking an ID and a ticket " "under a warm bulb, a red awning, and the show board.", @@ -391,7 +391,7 @@ SCREENS = [ "kind": "scene", "title": "TV Studio celebrity arrival", "source": "PLAN.md Late Night TV Studio — crowd gathers for an arrival", - "ref": None, + "ref": "13-studio-arrival.png", "description": "A celebrity arrival at the studio entrance: tourists and " "influencers pressing toward the door, cameras raised, the " "guard holding a line on the steps, studio marquee behind.",