# Gig Line Gig Line is a workplace security simulation about observation, communication, access verification, and proportionate escalation. The player works security at public-facing venues around Hollywood Boulevard, managing stairs, entrances, crowds, guests, staff, and occasional serious threats. The central design goal is simple: > The player wins by choosing the lowest level of escalation that keeps everyone safe. ## Screenshots | | | | --- | --- | | ![Venue overview: the Hollywood Boulevard entrance with steps, sidewalks, street lamps, and signage](docs/screenshots/01-venue-overview.png) | ![Targeting an approaching NPC with the interaction HUD](docs/screenshots/02-targeting-npc.png) | | ![Access inspection: wristband/credential check with allow/deny prompt](docs/screenshots/03-access-inspection.png) | ![Radio report form: emergency dispatch panel](docs/screenshots/04-radio-report.png) | | ![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) | | *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.* ## ⬇️ Downloads | Platform | Package | | --- | --- | | **Windows — installer** | [`gig-line-setup-.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · contains all game data, shortcuts + uninstaller | | **Windows — portable** | [`gig-line-windows-.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, no install needed | | **Linux — AppImage** | [`gig-line--x86_64.AppImage`](https://git.allucanget.biz/zwitschi/security-game/releases) · single file, `chmod +x` and run | | **Linux — portable** | [`gig-line-linux-.x86_64`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, `chmod +x` and run | | **macOS** | [`gig-line-macos-.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unsigned build, quarantine note below | | **Web (browser)** | [`gig-line-web-.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unzip and serve the folder from any static host (no special headers) | **All releases:** > 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.0** — Blender-authored character/gear/prop library with baked AO, higher-resolution environment textures, and phase-driven library props. > 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 > (NSIS + appimagetool) and uploads with the portable binaries. > > Note: on this host, downloading release files currently requires sign-in; > the page and tag URLs are public. > > macOS builds are unsigned: right-click → Open, or clear the quarantine with > `xattr -dr com.apple.quarantine gig-line-macos.zip`. Build instructions and > the release pipeline live in [`docs/RELEASING.md`](docs/RELEASING.md). If you are here to develop or test locally instead of downloading, jump to [Run and Test](#run-and-test) below. The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4.3 + GDScript): a procedural Hollywood Boulevard entrance, a first-person guard, simulated NPCs with intents and reactions, the six-level escalation model, access inspection and admission decisions, radio calls with credibility checks, reputation, crowd mood, and an end-of-shift review. ## Game Overview A typical shift lasts 10–15 minutes. The player: 1. Watches the entrance, steps, queue, and surrounding sidewalk. 2. Determines why each person is approaching. 3. Uses simple gestures or polite directions first. 4. Verifies authorized access using recognition, wristbands, credentials, tickets, or guest lists. 5. Escalates only when a person refuses directions, attempts unauthorized entry, or presents a genuine threat. 6. Records the incident through body-camera and radio systems. 7. Finishes the shift with a performance review based on safety, judgment, and professionalism. The game rewards calm resolutions rather than aggressive behavior. Emergency assistance and armed responders are rare last-resort systems with meaningful consequences. ## Planned Venues ### Hollywood Boulevard Building The original scenario places the guard at the top of the steps outside a fictional building. Tourists, street performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people experiencing a mental-health crisis, and rare violent threats may approach the entrance. Authorized people must either be recognized by the guard or present a valid wristband. Wristbands can have different colors, patterns, dates, or event markings. They may be valid, expired, counterfeit, or missing. ### 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. ### 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. 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. ## Escalation Model The common escalation model has six levels: 0. **Observation** — Decide whether intervention is necessary. 1. **Simple deterrence and polite direction** — Raise an open hand, make eye contact, point toward the sidewalk or check-in area, shake the head, or give a short instruction. 2. **Formal warning** — Clearly explain the rule and request compliance. 3. **Blocking and physical positioning** — Secure the entrance, maintain distance, preserve an escape route, and deny access without starting a fight. 4. **Radio for security assistance** — Request a supervisor, second guard, building management, or other appropriate support. 5. **Emergency response** — Hold the emergency-radio button and confirm only when there is an immediate threat, such as a weapon, assault, forced entry, or serious threat to occupants. An off-duty law-enforcement officer or armed emergency responder may arrive during a valid emergency. This character is not intended to be a routine stronger attack. Their role is crisis control, evacuation, restraint, arrest, or other appropriate intervention, with force governed by the situation and meaningful consequences for unjustified calls. ## Core Systems - **NPC behavior:** NPCs have different motivations, temperaments, compliance levels, and reactions to the player’s approach. - **Access verification:** Recognition, wristbands, IDs, tickets, credentials, and guest lists determine who may enter. - **Nonverbal deterrence:** Small actions such as raising a hand can resolve low-risk situations without dialogue or force. - **Dialogue:** Polite directions and warnings affect outcomes; simple gestures can resolve low-risk situations without dialogue. - **Escalation:** The player chooses the lowest effective response. - **Radio assistance:** Calls require useful information and may take time to arrive. - **Evidence:** Body-camera records support shift review and accountability. - **Crowd reaction:** Aggressive behavior, visible weapons, and emergency response can create panic or attract a crowd. - **Reputation:** Peaceful resolutions, accurate reports, low property damage, and justified calls improve performance; unnecessary confrontations and false reports reduce it. - **Shift review:** The player receives feedback on access decisions, escalation choices, public complaints, injuries, and security outcomes. ## Architecture Overview The planned solution uses a venue-driven simulation architecture. Venue configuration supplies rules, access types, NPC pools, equipment, events, and escalation policies. Shared simulation systems provide movement, perception, interactions, dialogue, access verification, escalation, radio calls, crowds, reputation, evidence, and shift review. ```text Venue Scenario / Rules | v Shift Coordinator -----> Event Scheduler | | v v NPC Simulation <---------- Interaction System | | v v Access Verification ----> Escalation / Response | | +-------------> Reputation / Evidence | v Shift Review ``` The architecture maps onto Godot 4 nodes, scenes, and custom resources as described in [Tech Stack](#tech-stack). See the full arc42-style outline in [`docs/architecture.md`](docs/architecture.md) and the decisions in [`docs/adr/`](docs/adr/). ## Tech Stack - **Engine:** Godot Engine 4.x - **Language:** GDScript - **Architecture:** Venue-driven simulation using nodes, scenes, and custom `Resource`-based scenario data - **Navigation:** `NavigationRegion3D` and `NavigationAgent3D` - **NPC behavior:** Custom lightweight finite state machines driven by `NpcProfile` resources - **UI and audio:** Godot `Control` nodes, themes, audio buses, and `AudioStreamPlayer` nodes - **Art assets:** authored PNGs and the in-game generated set in `assets/textures/ai/game_ready/`; street-furniture models in `assets/models/generated/` build from `tools/model_pipeline.py`; structural geometry stays procedural (`WorldBuilder`). The legacy `tools/generate_textures.py --ai` batch remains available. - **Asset tooling:** `tools/asset_pipeline.py` generates role-aware assets through OpenRouter (`--variants N` produces alternative images) and provides crop, resize, seamless-tile, and image-audit commands, including gameplay screenshot checks. `tools/model_pipeline.py` builds and audits the street-prop `.glb` models; `tools/npc_pipeline.py` builds and audits the skinned NPC base model. - **Testing:** GUT (Godot Unit Test) in headless mode - **Source control:** git on a Gitea host with Gitea Actions (`.gitea/workflows/build.yml`) for headless tests and desktop release builds - **Targets:** Windows, Linux, and macOS desktop first; web later ### Image Asset Tools Install the Pillow dependency used by the existing texture generator and the asset tools: ```bash python3 -m pip install -r requirements-tools.txt ``` Generate a role-sized image (generation requires `OPENROUTER_API_KEY`): ```bash python3 tools/asset_pipeline.py generate --prompt "weathered red brick" --role tile --output assets/textures/brick_extra.png ``` Generated images are resized to the selected role profile. Non-square requests use the validated `openai/gpt-5-image-mini` source ratio and crop to the final role dimensions. Transparent glyphs can be recolored while preserving their alpha mask with `--foreground '#ffffff'`. Crop uses the image center by default; `--x` and `--y` select an explicit crop origin. Resize defaults to aspect-preserving `contain`; use `--fit cover` to crop or `--fit stretch` to distort to the exact dimensions. ```bash python3 tools/asset_pipeline.py crop assets/textures/source.png assets/textures/crop.png --width 256 --height 128 python3 tools/asset_pipeline.py resize assets/textures/source.png assets/images/scenes/scene.png --width 1920 --height 1080 --fit cover python3 tools/asset_pipeline.py seamless assets/textures/rough_tile.png assets/textures/tile.png ``` Audit accepts image files or directories. `--role auto` infers common roles from paths and filenames; explicit roles include `tile`, `texture`, `icon`, `card`, `banner`, `backdrop`, `background`, `overlay`, `app_icon`, and `screenshot`. ```bash python3 tools/asset_pipeline.py audit assets/textures python3 tools/asset_pipeline.py audit assets/images/scenes --role background --json python3 -m unittest discover -s tools -p 'test_*.py' ``` The audit classifies measurable fit issues as `good` or `bad` based on role-specific dimensions, aspect ratio, alpha requirements, and tile-edge continuity. It lists perspective/projection as a visual review item because image semantics cannot be judged reliably by these deterministic checks. ### Prop models Street furniture (bollards, benches, bins, planters, newspaper boxes, a hydrant) is real 3D geometry generated by `tools/model_pipeline.py` and written to `assets/models/generated/*.glb`. Godot imports each file as a `PackedScene`, and `WorldBuilder` instantiates it with a procedural fallback when the model or its import is missing, so the world still builds on a fresh checkout. ```bash # Print the catalogue (parts, triangle count, whether a model/concept exists) python3 tools/model_pipeline.py list # Rebuild the models (deterministic, offline) and validate them python3 tools/model_pipeline.py build python3 tools/model_pipeline.py audit # Optional: an image model supplies the colour palette per prop # (requires OPENROUTER_API_KEY; the repo .env is read automatically) python3 tools/model_pipeline.py concept python3 tools/model_pipeline.py concept --only bench --force ``` Each prop is a small list of boxes and cylinders, so `build` needs no network and the output is byte-identical between runs. `concept` asks an image model for a studio-style reference; `dominant_colors` and `assign_roles` then map that image's colours onto material roles (darkest → metal, warm saturated → wood, light grey → stone, green → foliage). glTF stores colours in linear space, so the tool converts the sRGB palettes before writing them. Re-run `build` and `godot --headless --path . --import` after changing a prop. See [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md). Surface materials share one definition in `src/game/materials.gd`: named roughness/metallic profiles per surface, plus the environment tune-up (ACES tonemapping, glow, fog, colour grading) that the phase lighting applies. ### NPC character model NPCs are moving from the procedural capsule rig to a skinned, UV-mapped base character authored in Blender ([ADR-0013](docs/adr/0013-blender-authored-npc-characters.md)). `tools/npc_pipeline.py` builds `assets/models/generated/npc_base.glb` plus the per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its five material slots from the seed-driven outfit, and drives the bones from the existing animation code; the procedural rig remains the offline fallback (see the [graphics plan](docs/plan-graphics-v0.6.md)). ```bash # Print the frozen contract (slots, bone names, triangle budget) python3 tools/npc_pipeline.py list # Rebuild the skinned base mesh + neutral maps in Blender and validate it flatpak run org.blender.Blender --background --python "$PWD/tools/npc_pipeline.py" -- build python3 tools/npc_pipeline.py audit # Layer the AI clothing albedos (shirt/trousers) over the procedural weave, and # derive per-fabric outfit colours for the avatar palette python3 tools/npc_pipeline.py prepare python3 tools/npc_pipeline.py palette ``` `prepare` resizes the AI fabric tiles (kept locally in `assets/textures/npc/ai/`, which is gitignored) onto the `cloth`/`pants` albedos; `build` bakes them into the `.glb` and regenerates the procedural normal + packed ORM maps. `palette` writes `assets/textures/npc/palette.json` using `model_pipeline.dominant_colors`. ### Blender asset library (loaded by the game) `tools/blender_assets.py` models a larger library procedurally in Blender for the v0.6.0 integration: **8 skinned NPC characters** (casual, jacket, coat, hi-vis vest, dress, hoodie, firefighter, guard), each a drop-in for `npc_base.glb` (same bones, joint positions and tint slots); **18 gear pieces** authored at head/hand/back/chest attach points (caps, hood, helmet, backpack, SCBA tank, lanyard, phone, cup, clipboard, umbrella, camera, sign, flashlight, wand, radio, …); and **20 props** (octagonal bollard and bin, slatted bench, hydrant, news box, planters, street lamp, street and palm trees, A-frame sign, wall lantern, cone, crowd barrier, stanchion, handrail, podium, double door, marquee, sedan). `NpcAvatar` selects a character by role/seed, tints the five contract slots, and attaches gear at the documented points; `WorldBuilder` loads the library props with colliders/avoidance and drives their emissive parts from the day/night cycle. The single `npc_base.glb` and primitive props remain offline fallbacks. Contract and integration notes: [`docs/blender-assets.md`](docs/blender-assets.md). | Characters | Props | | --- | --- | | ![characters](docs/blender_previews/sheet_npc.jpg) | ![props](docs/blender_previews/sheet_props.jpg) | ```bash flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg python3 tools/blender_assets.py audit # no Blender needed godot --headless --path . --import ``` ### Confirmed game assets Everything below is audited and shipped in the game; development-only outputs (AI concept sheets, Blender sources, raw AI fabrics) are gitignored. | | | | --- | --- | | ![brick facade](assets/textures/ai/game_ready/v2/picks/brick_facade.png) | ![sidewalk pavers](assets/textures/ai/game_ready/v2/picks/sidewalk_pavers.png) | | ![asphalt](assets/textures/ai/game_ready/v2/picks/asphalt.png) | ![awning](assets/textures/ai/game_ready/v2/picks/awning_canvas.png) | | ![skyline day](assets/textures/ai/game_ready/v2/picks/skyline_day.png) | ![skyline night](assets/textures/ai/game_ready/v2/picks/skyline_night.png) | - **Surface textures:** the v2 picks above (brick, sidewalk pavers, dry and wet asphalt, awning canvas) plus their normal maps, loaded by `src/game/materials.gd` and `WorldBuilder`; the three skyline panoramas are switched per shift phase. The first-generation tiles and `assets/textures/skyline.png` are no longer loaded. - **UI art:** `assets/textures/ai/game_ready/icons/` (flashlight, door lock, wand, REC) and `assets/textures/ai/game_ready/cards/` (ID, wristband, ticket, credential, guest list), loaded by `src/game/ui/hud.gd`. - **Generated models:** `assets/models/generated/*.glb` — bollard, bench, trash bin, planter, newspaper box, hydrant, and the NPC base — plus their colour atlases. - **NPC material maps:** `assets/textures/npc/*_{albedo,normal,orm}.png` and `palette.json`. Audit them all: ```bash python3 tools/asset_pipeline.py audit assets/textures/ai/game_ready python3 tools/model_pipeline.py audit python3 tools/npc_pipeline.py audit ``` ### Realism pass: scene concepts, skylines and textures The skyline, surface tiles and phase lighting were rebuilt against AI concept frames. The concepts live in [`docs/screens/concepts/`](docs/screens/concepts/README.md); the validated picks the game loads are in `assets/textures/ai/game_ready/v2/picks/` (day/dusk/night panoramas, brick, sidewalk pavers, dry and wet asphalt, awning canvas, each with a normal map). `tools/scene_concepts.py` generates and processes new candidates, promotes the validated ones to stable names, audits them, and scores captures against the concepts. It never overwrites an image: new files get a `-vN` suffix, and `process` skips existing candidates unless `--new`. ```bash python3 tools/scene_concepts.py generate --group scene # or backdrop / texture, --variants N python3 tools/scene_concepts.py process && python3 tools/scene_concepts.py audit python3 tools/scene_concepts.py promote # validated picks -> v2/picks/ godot --headless --path . --import # A/B a candidate without touching game code: the same cameras, textures swapped at runtime. godot --path . --script res://src/test/capture_realism.gd -- --out=res://docs/screens/realism//after godot --path . --script res://src/test/capture_realism.gd -- --out=res://docs/screens/realism//try \ --swap=res://assets/textures/ai/game_ready/v2/picks/brick_facade.png=res://assets/textures/ai/game_ready/v2/brick_facade-v2.png # Score before/after against the concepts (Pillow detail metrics + vision judge). python3 tools/scene_concepts.py realism --run docs/screens/realism/ --judge \ --model google/gemini-2.5-flash --repeats 3 ``` `capture_realism.gd` runs five stages (day, evening, late, Joke Shop, studio); `--stages=day,late` limits it while tuning. The latest run, [`docs/screens/realism/2026-11-01-adopt/`](docs/screens/realism/2026-11-01-adopt/report.md), compares the pre-adoption captures with the current build: on the shots both sets captured the judge's like-for-like means rose on every sub-score (overall 0.8 → 1.0, backdrop 0.6 → 1.6) and the Pillow metric gap fell 0.444 → 0.348. ![realism run](docs/screens/realism/2026-11-01-adopt/sheet.jpg) ### Screen design references Screen mockups and screenshot comparison use `tools/screen_designs.py`. It turns the screen descriptions in `docs/` (README captions, ADRs 0007–0010, `PLAN.md`, and `architecture.md`) into runtime reference art under `assets/images/ui/` (with a reference-only snapshot in `docs/screens/designs/`), then uses those images as the expected state to check the game's captures. Each screen is tagged `scene` (a realistic first-person 3D world view) or `ui` (an in-game panel overlaid on a softened scene), and the generator appends a shared art-direction suffix so the frames read as one coherent game. Requests use a 3:2 landscape source and crop to the 16:9 capture dimensions. ```bash # Export the built-in scenarios to editor-authorable .tres resources godot --headless --path . --script res://tools/export_venues.gd # List the catalogue (name, kind, whether a reference design exists) python3 tools/screen_designs.py list # Deterministic wireframes (offline, no API key); existing designs are kept # unless --force is passed, so an offline run never replaces an AI mockup python3 tools/screen_designs.py generate --draft python3 tools/screen_designs.py generate --draft --only wand --force # Realistic 3D scenes and in-game screen designs via OpenRouter # (requires OPENROUTER_API_KEY) python3 tools/screen_designs.py generate # Prop gallery (studio line-up) for close-range prop checks godot --path . --script res://src/test/capture_prop_gallery.gd # Compare docs/screenshots/*.png against the designs python3 tools/screen_designs.py compare python3 tools/screen_designs.py compare --json # Ask a vision model whether each capture realises its design's screen and layout # (requires OPENROUTER_API_KEY; the repo .env is read automatically) python3 tools/screen_designs.py judge python3 tools/screen_designs.py judge --model openai/gpt-4o-mini --json python3 tools/screen_designs.py judge --only 03- # one capture python3 tools/screen_designs.py judge --dry-run # build requests, no network ``` The catalogue covers 21 screens: the eleven README captures plus the start menu, pause overlay, settings, shift review, radial menu, emergency radio, wand screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival, body-camera timeline, and the escalation HUD. All 21 have an AI reference in `assets/images/ui/` (snapshot in `docs/screens/designs/`); the eleven screens with staged captures are also compared or judged against the running game. `compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank frame, and large layout/palette/detail distance. It is a regression gate, not a semantic judge — pixel and perceptual metrics cannot decide whether a frame's composition is *right*. `judge` covers that gap: it sends each capture together with its concept mockup to a vision model and asks whether the capture realises the same screen and layout intent. Art style, fidelity, fonts, and placeholder text are explicitly ignored. Verdicts are classed as `match`, `review` (a mismatch below `--fail-confidence`, default 0.6 — expected when a concept mockup is more stylised than the game), or `diff` (a confident mismatch, which exits non-zero). Regenerate the designs after changing screen layout, then re-run `compare` after regenerating the game screenshots with `src/test/capture_screenshots.gd`. For a one-command drift check, run `tools/check_screenshots.sh`. It renders the staged captures and prop gallery into `docs/screenshots/` (using `xvfb-run` when no display is available) and then runs `screen_designs.py compare`. ### Setup 1. Download the Godot 4.3 editor (official build) and put `godot` on your PATH. 2. Open or run the project from this directory. ### Run and Test ```bash # Open in the editor godot --editor --path . # Run the game godot --path . # Headless smoke boot (120 frames then exit) godot --headless --path . --quit-after 120 # Run all unit tests headlessly (exit code 0 = pass) godot --headless --path . --script res://src/test/test_runner.gd # Save a screenshot to user:// (windowed) godot --path . --script res://src/test/take_screenshot.gd # Windowed control smoke: focus circle, action wheel, wheel cycling + right-click clear; saves PNGs, exits 1 on failure godot --path . --script res://src/test/smoke_focus_radial.gd # Session-shell smoke: menu → shift → pause → settings → menu (headless or windowed) godot --headless --path . --script res://src/test/smoke_session_shell.gd # PLAN-verb smoke: recognize, request reason, body camera, warning, blocking godot --headless --path . --script res://src/test/smoke_plan_verbs.gd # Prop-collision smoke (windowed): guard stops at a bench, guest routes around it godot --path . --script res://src/test/smoke_props.gd # NPC budget smoke: 16 full-rig avatars against the documented frame budget godot --headless --path . --script res://src/test/smoke_npc_budget.gd # Movement smoke (windowed): steps, precision walk, footsteps and landing godot --path . --script res://src/test/smoke_movement.gd # Crowd smoke: rubbernecking on aggression, panic clearing the entrance godot --headless --path . --script res://src/test/smoke_crowd.gd # Emergency smoke: unjustified call, investigation note, evacuation aftermath godot --headless --path . --script res://src/test/smoke_emergency.gd # Body-camera timeline smoke: review → timeline → step through → back to review godot --headless --path . --script res://src/test/smoke_timeline.gd # Audio smoke: crowd bed follows mood, radio open/close crackle godot --headless --path . --script res://src/test/smoke_audio.gd # Scenario-content smoke: wand refusal, celebrity crowd spike godot --headless --path . --script res://src/test/smoke_scenario_content.gd ``` The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor. ### Controls Keyboard and mouse are the default; a gamepad works without configuration (left stick moves, right stick looks, A opens/confirms the wheel, B cancels, X inspects, Y calls the radio, LB walks slowly, RB uses the wand). Keys and pad buttons can be rebound in Settings, and the remaps persist in `user://settings.json`. The HUD legend switches to gamepad hints when a pad event arrives. | Input | Action | | --- | --- | | W/A/S/D + mouse | Move and look | | Space / left click | Action wheel for the focused person (Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio) | | A / D or ←/→ | Step through the action wheel (while it is open) | | Space / left click | Confirm the highlighted action (while the wheel is open) | | Esc / right click | Close the top panel; Esc with nothing open pauses the shift (Esc again resumes) | | ← / → or ↑ / ↓ | Navigate the venue menu and the pause / settings panels | | Enter | Start the highlighted venue from the menu, or confirm in a panel | | 1 / 2 / 3 | Quick gestures without the wheel: raise hand, point, head shake (Level 1) | | Q | Polite direction (Level 1) | | 4 | Formal warning (Level 2) | | 5 | Blocking and positioning (Level 3) | | 6 | Security assistance radio (Level 4) | | R | Emergency radio, hold on-screen button ~1.6s (Level 5) | | E | Inspect access artifacts / close inspection | | C | Handheld wand screening (wand venues only) | | A / D | Allow / deny entry during inspection or wand check | | V | Cycle venue (debug shortcut; restart via V restarts the current shift) | | Tab / mouse wheel ↓ / ↑ | Cycle the target among nearby NPCs, nearest first (aim-free; the target stays selected) | | Right click (nothing open) | Clear the focused person and return to aim-based focus | | Shift (hold) | Precision walk at roughly half speed for close work at the door | | G | Toggle flashlight (widens NPC perception at night) | | B | Toggle the body camera; a formal warning switches it on automatically | | L | Lock / unlock the restricted door (thwarts forced entry) | | F | Toggle debug overlay | ## Architecture The full arc42-style architecture documentation lives in [`docs/architecture.md`](docs/architecture.md). It covers goals, constraints, context, solution strategy, building blocks, runtime views, deployment, cross-cutting concepts, quality requirements, risks, a glossary, the CI/CD build pipeline, semantic versioning, and release management. ## Project Documentation - [`PLAN.md`](PLAN.md) — Game design plan and scenario details. - [`AGENTS.md`](AGENTS.md) — Development and collaboration workflow. - [`TODO.md`](TODO.md) — Active checkbox task list. - [`DONE.md`](DONE.md) — Completed task sections. - [`MEMORY.md`](MEMORY.md) — Persistent session notes. - [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation. - [`docs/adr/`](docs/adr/) — Architecture Decision Records. - [`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md) — Graphics-quality plan: Blender-authored NPC characters. - [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points. ## Development Status 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: **227 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`) - Session shell: venue start menu (keyboard + mouse), Esc pause overlay (resume / restart / settings / quit to menu), settings panel with mouse-look sensitivity and HUD font scale persisted to `user://settings.json`, and "Run another shift" returning to the venue menu - World presentation: skyline backdrop rendered with an alpha material, walkable step and landing colliders, larger marquee text, and larger focus circle / name tag - Routine traffic despawns after a while at its waypoint so the crowd cap no longer fills with idle standers - PLAN verbs: eye contact (distinct Level 1 cue), recognizing a person as a player decision recorded as access evidence, a body-camera flag that a formal warning arms (and `B` toggles), blocking that physically stations the guard between the person and the door, and a Level 3 request-reason ask answered from the NPC's `dialogue_lines` - Incidents are now actually opened on focus and closed at shift end, so player actions, access evidence, escalation levels, and unresolved interactions reach the review (previously every recorded event was dropped) - Crowd presentation: aggressive behavior and responder arrivals make idle bystanders stop and face the incident; panic sends people away from the door (and they leave the scene). A busy 16-NPC entrance measured ~1.2 ms/frame, so no LOD/culling was needed at the current cap - Distant skyline ring: four alpha-scissored planes at 200 m read as a hazy city on the horizon (the baked backdrop used to show as a black slab), with a larger ground plane so the buildings meet the street - Flashlight aims straight ahead and lights the person/door being checked instead of the ground in front of the guard - Emergency aftermath: the `evacuation` outcome locks the door and clears the entrance, an emergency call that the situation did not justify opens an investigation and applies the false-report hit, and the shift review names the responder outcome and any investigations - Body-camera timeline: the shift review opens a text timeline of every recorded incident event (time, person, action/outcome) that you step through with the arrow keys or buttons. No live 3D replay yet - NPC theater: seated guests sit on the steps with bent legs, influencers and tourists raise a camera prop while photographing, performers sway their sign, and filming visitors pan slowly — all once they reach their intent's waypoint - Audio: a looping procedural crowd bed (filtered murmur) whose volume follows `CrowdSystem.mood`, plus radio open/close crackle. All generated at runtime, no audio assets, and safe with a dummy audio server - Accessibility: the escalation cue shows a shape meter plus the numeric level (`Escalation: Blocking [###--] L3/5`) so it reads in grayscale, and the HUD font scale from settings is applied at boot by resizing fonts (not Control.scale, which broke the right-aligned layout) - Editor-ready venues: `tools/export_venues.gd` writes `content/venues/.tres` from `ScenarioFactory`, and the `.tres` files load back with events, profiles, policy, phases, door lines, and locations intact (the factory stays the source of truth) - Localization safety: `Strings.fmt` validates the placeholder count before formatting and fails safe (warns, returns the template) instead of raising an engine error, `Strings.placeholder_count` ignores `%%`, and `Strings.validate()` lints the table for trailing `%`, unsupported conversions, and mixed positional/sequential placeholders - Authored scenario content: Joke Shop reservation-without-ID, borrowed/mismatched ID, a guest who refuses the wand screen, and a late arrival during a performance; Studio filming-boundary repeats and a celebrity arrival that draws a small crowd; Building events that point the player at the body camera and request-reason verbs - Visual polish pass (v0.2.0): phase-based lighting (sun/sky/ambient per shift phase), street lamps with night-mode bulbs and omni lights, glowing marquee signage, crosswalk, planters and low-poly street trees, NPC height/brightness variety, denser routine traffic — day and late-night screenshot sets - Assets pass: authored textures in `assets/textures/` (brick facade, concrete, asphalt, awning, skyline, vignette, HUD icons, access-card art) generated by `tools/generate_textures.py`, soft sun shadows, humanoid NPC rigs with archetype props + procedural walk bob and attention head-turn, inspection artifact cards, escalation vignette, mode icons, and a body-camera overlay (REC + shift clock) - Rendering pass (v0.3.0 work, [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md)): one material module with roughness/metallic profiles per surface, ACES tonemapping, glow, aerial fog and colour grading driven by the shift phase, correctly scaled brick, framed facade windows that light up at night, door trim and handles, a curb and lane dashes, and the glowing marquee/lamp bulbs. NPCs gained per-role clothing palettes, caps/hoods/backpacks/sunglasses/lanyards, hands and shoes, and an arm/leg walk swing - NPC characters (v0.5.0): articulated limb rigs, layered outfits, hand items, face/hair variety, idle behaviour (phone, clipboard, crossed arms, look-around), escalation gestures, readable reaction symbols, door-queue offsets, and three LOD tiers with a billboard impostor - Player movement (v0.5.0): separate acceleration/braking, step-up assist, head bob and landing dip (toggleable), precision-walk FOV/chip/tick, surface-aware footsteps, gamepad support, and persistent key/button remapping in settings - Prop collision and placement (v0.4.0): every prop fits a static collider from its mesh bounds and publishes a navigation obstacle, so the guard and guests cannot walk through benches, bins, or bollards; placements are grouped into zones with a validator, and deterministic variants add rotation, scale, and small clutter - Prop atlas (v0.3.1): `tools/model_pipeline.py` packs each prop's palette into one power-of-two colour atlas plus a metallic/roughness atlas and emits a single material with per-vertex UVs, so a prop is one draw material rather than one per part; `audit` enforces the contract - Generated prop models (v0.3.0 work): `tools/model_pipeline.py` builds bollards, benches, bins, planters, newspaper boxes and a hydrant as `.glb` props with per-part materials, loaded by `WorldBuilder` with a procedural fallback; the optional AI concept step (`model_pipeline.py concept`) supplies per-prop colour palettes - Environment pass (v0.6.0): 1024² brick/concrete/asphalt with derived tangent normal maps, 256² baked AO maps for every Blender character/gear/prop, and library props whose bulbs/LEDs/sign faces follow the day/night phase - NPC characters + library (v0.6.0): `NpcAvatar` loads one of 8 Blender-authored skinned variants (role/seed selected) with five tintable material slots, `Rig/Head` focus anchor, and attach-point gear; `npc_base.glb` and the procedural rig remain fallbacks. `tools/blender_assets.py` builds/audits the character, gear, and 20-prop library; `WorldBuilder` loads the replacements and new street dressing (lamp, trees, palms, planters, barriers, podium, doors, marquee, sedan, handrail) with colliders, navigation obstacles, and phase-driven emissives - Web build: `build/gig-line-web-.zip` (Godot's non-threaded web template, no COOP/COEP headers needed) built by `tools/build_releases.sh` and published with the desktop artifacts; serve the unzipped folder from any static host - Aim-free selection pass (v0.3.0 work, [ADR-0011](docs/adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work 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`). ### Smoke Test Checklist 1. `godot --path .` starts the game on the venue menu without script errors. 2. Choosing a venue (click or ↑/↓ + Enter) starts that venue's shift and hides the menu. 3. The guard moves and looks around; the building, steps, and sidewalk are visible. 4. The guard's camera sits at eye height and the guard stands on the landing, not inside the building. 5. Walking into the steps carries the guard up onto the landing (steps have colliders). 6. The skyline backdrop shows silhouettes against the sky, not an opaque black slab. 7. The `GIG LINE` marquee is readable from the sidewalk. 8. NPCs spawn and approach the entrance during the first minutes. 9. Looking at an NPC inside the focus cone shows a blue focus circle in front of the face, the person's name, and the HUD target line. 10. `Space` or left click opens the action wheel; `A`/`D` highlight a slice, `Space` confirms it, `Esc`/right click cancels without a log entry. 11. `E`/`C` open the inspection/wand panels from the wheel and freeze movement until they close; walking never fires the wand. 12. Pressing `Q` produces a reaction log entry and reputation feedback. 13. `E` opens access inspection showing venue-correct artifacts and policy result; `A`/`D` record a decision. 14. `6` opens the assistance form; submitting shows dispatch feedback. 15. `R` opens the emergency form; the button must be held ~1.6s or the call cancels. 16. `G` toggles the flashlight (indicator appears in the HUD); `L` toggles the door lock (indicator turns red when locked). 17. `F` toggles the debug overlay with NPC states and shift stats. 18. The wheel's `Look` slice is eye contact (Level 1) and logs as its own action. 19. `Reason` asks why the person is there and logs their `dialogue_lines` reply. 20. `Recognize` records the guard's recognition as access evidence; pressing it twice says so. 21. `Warn` turns the body camera on (REC overlay appears); `B` toggles it manually. 22. `Block` moves the guard between the person and the door. 23. The shift review reports the incident count, including interactions left unresolved. 24. Escalating on someone (or a responder arriving) makes idle bystanders stop and face the incident. 25. Repeated alarms panic the crowd and nearby people clear away from the door instead of standing around. 26. `Esc` with nothing open pauses the shift; resume / restart / settings / quit-to-menu all work, and settings changes persist across a restart. 27. NPCs route around the building using the navigation mesh (no stuck-at-wall behavior during a smoke run). 28. An unjustified emergency call (calm person, no weapon) logs an investigation and costs reputation. 29. After an evacuation outcome the door is locked and NPCs are leaving the entrance. 30. The shift review's "Body camera timeline" button steps through the recorded events (Left/Right or the buttons) and Esc returns to the review. 31. The escalation label shows a `[###--] L3/5` shape meter as well as the level name, and stays readable in grayscale. 32. Changing "HUD font scale" in Settings resizes the HUD text and persists across a restart. 33. When the shift timer reaches 0, the shift review screen shows grade, incident summary, responder outcomes, investigations, and persisted shift history; "Run another shift" returns to the venue menu. ## Development Workflow Follow [`AGENTS.md`](AGENTS.md): inspect the repository first, record work as checkbox tasks in `TODO.md`, move completed sections to `DONE.md`, update `MEMORY.md` with durable context, and keep this README synchronized with setup and architecture changes.