Compare commits

..
114 Commits
Author SHA1 Message Date
zwitschi 9d37e0a1c5 Bump the project version to 0.9.0
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Successful in 5m0s
2026-11-01 21:48:03 +01:00
zwitschi 883626a067 Ship the 0.9.0 production-quality milestone
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Canceled after 4m58s
Add mixer/accessibility settings, caption/reduce-motion hooks, audio volume
clamping, a 24-NPC performance smoke and shortened coat/dress stride for hem
clipping. Record the explicit Godot 4.3-CI/4.7-local matrix and the 0.9
production scope. Headless 250/250, Python 94/94, 24-NPC budget smoke passes.
2026-11-01 21:46:42 +01:00
zwitschi b5d4d5fb87 Plan detailed 0.9.0 production-quality steps
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
Expand the next milestone into animation, audio, localization, accessibility,
performance/platform, QA/visual baselines, Godot-version policy and exit
criteria, with concrete verification work per workstream.
2026-11-01 21:39:29 +01:00
zwitschi fac9b002c3 Fix normal radio report submission
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Failing after 4m22s
The radio button was connected only to the emergency hold handlers, so a
normal Send request never reached _submit. Connect pressed for non-emergency
calls while keeping hold-to-confirm exclusive to emergencies, and add tests
for click, short-hold rejection and full emergency hold. Headless 249/249,
Python 94/94.
2026-11-01 21:37:39 +01:00
zwitschi aa51ee331e Ship the 0.8.0 first-hour and campaign milestone
Full build / Import + headless tests (push) Successful in 41s
Full build / Full desktop build + optional release (push) Successful in 4m18s
Add the campaign shell, pause-safe checkpoint resume, post-shift campaign
continue, confirmed campaign reset, difficulty presets, searchable Help,
content-matrix validation, recovery events, contextual dialogue, policy/debrief
context and eligible-event previews. Extend persistence with atomic separate
files and add campaign/checkpoint/tutorial smokes. Headless 247/247, Python
94/94, tutorial/campaign/session/scenario smokes green.
2026-11-01 21:21:43 +01:00
zwitschi 86332cd401 Wire the first-shift tutorial into gameplay
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Add the non-blocking prompt card, safe compliant visitor, action-driven
advancement through the real wheel/inspection/warning/radio panels, persisted
completion/skip, a Settings replay action and localized tutorial strings.
Scripted tests/captures bypass the tutorial unless --tutorial is passed. Add a
windowed smoke covering the full flow. Headless 237/237, Python 94/94, tutorial
smoke passes.
2026-11-01 21:05:59 +01:00
zwitschi 76eb5e3a79 Fix mouse look swallowing by the HUD
Full build / Import + headless tests (push) Successful in 35s
Full build / Full desktop build + optional release (push) Successful in 4m39s
Move guard look handling to the _input stage, recapture the cursor on
window-focus return and when clicking back into gameplay, and make HUD controls
mouse-transparent. Add regression tests for yaw/pitch and HUD mouse filters.
Verified under Xvfb with real relative mouse motion. Headless 237/237, Python
94/94, movement smoke 60 fps.
2026-11-01 20:38:36 +01:00
zwitschi f4a53e2d4f Add the first-shift tutorial design and state machine
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 38s
Write the eight-beat tutorial sheet, its safe visitor script, persistence and
replay rules. Add a deterministic, skippable/restartable Tutorial RefCounted
state machine with headless tests. UI/GameManager wiring remains the next task.
Headless 235/235, Python 94/94.
2026-11-01 20:19:18 +01:00
zwitschi 806fe78688 Clarify nested TODO completion rules
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 37s
Describe level-2 milestones/themes, level-3 workstreams and the rule that
completed workstreams stay under their parent until the whole milestone is
done. Add a milestone-shaped recommended format and align the changelog/tag
steps with nested sections.
2026-11-01 20:16:23 +01:00
zwitschi 6a3066d763 Clarify nested task grouping in the agent checklist format 2026-11-01 20:14:53 +01:00
zwitschi f4cce28454 Restructure the v1.0 roadmap around milestone sections
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Promote every milestone to a level-2 section and expand 0.8.0 into detailed
workstreams for tutorial, campaign, content matrix, dialogue, debrief, save/
resume, difficulty/help and verification. Keep later milestones broad and
preserve the deferred realism/future lists.
2026-11-01 20:12:17 +01:00
zwitschi 7d39523e49 Keep generated realism runs out of the repository
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 33s
Ignore docs/screens/realism/ entirely and remove its reports/captures from the
index while leaving the generated folders on disk. Update the asset-tooling and
concept notes to treat realism runs as local QA artifacts. Link scan clean.
2026-11-01 20:09:45 +01:00
zwitschi 7e084192ce Remove the obsolete graphics plan
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Skipped
Delete docs/plan-graphics-v0.6.md and update README, architecture, asset
tooling, Blender docs and MEMORY references. Completed work stays summarized
in ADR-0013, the asset-pipeline architecture and the changelog; the v1.0.0
roadmap owns future graphics work.
2026-11-01 20:06:31 +01:00
zwitschi 6c367e8c7e Consolidate architecture docs and add the v1.0.0 roadmap
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Skipped
Make architecture the canonical system/data-flow source, add explicit
documentation ownership and consolidation rules, and mark the old graphics plan
historical. Fix stale README/status/Blender/release claims. Add a full gap
register and milestone roadmap from v0.8 through v1.0.0, including onboarding,
content depth, accessibility/localization, performance, release/legal blockers
and non-blocking future ideas.
2026-11-01 20:04:47 +01:00
zwitschi 659b0e35b2 Make the vision judge discover cost-effective OpenRouter models
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Successful in 4m6s
Add openrouter_judge.py to rank live image+JSON models by advertised price,
with ordered fallbacks, auth-error handling and auto selection. Wire both judges
to --model auto, record the model used, and add fake-catalogue/mocked-call
tests. Re-run the judges with the valid key: 13/13 screen designs match, 0 diff;
the Forward+ A/B report records the selected model and ~$0.006 spend. Headless
231/231, Python 94/94.
2026-11-01 19:55:43 +01:00
zwitschi 28f2a48811 Add the optional Forward+ desktop HQ build
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Successful in 5m20s
Package a Forward+ preset with volumetric fog, SDFGI and a volumetric flashlight
for a separate Linux/Windows Desktop HQ zip. Gate the effects on a real
RenderingDevice, tune LATE fog, and adapt local light falloff for Godot 4.7.
Record the Compatibility/Forward+ A/B captures and metrics, add the artifact to
the build/publish scripts, and keep the default Compatibility/Web path
unchanged. Headless 231/231, Python 87/87; exported HQ binary boots with Vulkan.
2026-11-01 19:40:42 +01:00
zwitschi 64f0c6a198 Document the local Godot 4.7.2 toolchain
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 38s
Record the upgraded 4.7.2 binary, the retained 4.3 backup and the matching
export templates. Keep the project/CI baseline at 4.3 and note that 4.7.2
verified import, tests, the session smoke and a Linux export. Hide the
4.7-generated per-script UID sidecars from this clone only.
2026-11-01 19:16:55 +01:00
zwitschi 8b0845bcce Build distinct Joke Shop and TV Studio venues
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Successful in 4m57s
Add the alley walls, warm lantern, shutters, dumpster, AC clutter and door
step for the Joke Shop. Build the studio stone facade, bulb canopy, glossy
black car, stanchions and barriers. Rebuild only the world container when the
venue changes, so the guard and UI survive; add per-venue navigation checks.
Capture gallery shots 12/13, update the concept mapping and docs. Headless
231/231, Python 87/87, screenshot gate 13 good/0 bad. The vision-judge retry is
tracked separately because the API key is invalid.
2026-11-01 19:09:47 +01:00
zwitschi 4cca2af2dd Extract per-venue geometry specs into WorldBuilder
Add the Hollywood, Joke Shop and TV Studio tables for facade, window,
stair, street, nav, guard-spawn and feature data. Unknown ids fall back to
the boulevard. Use the specs for the core dimensions and gates, with a headless
test for the distinct venue data. Headless 230/230.
2026-11-01 19:03:20 +01:00
zwitschi 99f390078d Restore the full changelog history 2026-11-01 19:00:53 +01:00
zwitschi a9fe6b9944 Extend the agent workflow with docs and changelog maintenance 2026-11-01 18:56:15 +01:00
zwitschi d4201caa62 Release v0.6.1
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Successful in 3m49s
Ship the fitted NPC gear pass with the post-v0.6.0 realism and UI work.
Bump the version source and documentation to 0.6.1 and move the changelog
entry out of Unreleased. Headless 229/229.
2026-11-01 18:55:58 +01:00
zwitschi 6100d19a25 Document the gear-fit pass and add the changelog
Create CHANGELOG.md with the post-0.6.0 gear-fit entries and backfill the
recent release history. Update the implemented-features list, asset-tooling
notes, README links and verification counts (headless 229, Python 87).
2026-11-01 18:55:43 +01:00
zwitschi a7ca698f73 Break the README into focused docs pages
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Skipped
2026-11-01 18:47:44 +01:00
zwitschi 26d64baa01 Close the NPC gear-fit section
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 34s
Move the completed gear section to DONE.md and record the hooded-portrait
follow-up. An explicit --portrait-type now overrides the hooded-subject skip,
though the day roster has no types 5-7 yet. Headless 229/229.
2026-11-01 18:12:37 +01:00
zwitschi 151630e957 Recapture the gear portrait and gallery shots
Fix the portrait staging: face the rig toward the camera (the old yaw was 180
degrees out) and hide the focus disc, which was covering the face in close-ups.
Add the before/after Influencer portrait, fit measurements and judge scores in
docs/screens/realism/2026-11-01-gear. Re-run check_screenshots (11 good/0 bad);
headless 229/229.
2026-11-01 18:09:50 +01:00
zwitschi 1b319c48b7 Guard gear fit with headless and Blender regression checks
The Python fit test now caps the glasses at 0.70x head width and requires a
positive bag clearance on both the coat and dress. The Godot character suite
loads head/back gear on six roles and checks attach parents, head width and
brow bounds, and that back gear reaches beyond the 0.26 m body silhouette.
Rebuilt and re-baked all 18 gear assets; no-op GLB export churn was reverted.
Headless 229/229, Python 87/87.
2026-11-01 18:04:51 +01:00
zwitschi a478bfbf35 Clear the firefighter helmet brim from the brow
The face pass added a brow ridge; lift the helmet mesh 2 cm so the brim sits
above the eyes. Cap and hood were rendered on their characters and leave the
brow/ears and face readable. Rebuild and re-bake the helmet; Python 87/87.
2026-11-01 18:02:38 +01:00
zwitschi 702f54ae53 Move the shoulder bag clear of coat and dress skirts
Narrow the bag to 0.06 m and move it outboard to centre x=0.292. The body inner
face is now 0.262 m, clearing the coat skirt (0.225) and dress (0.260); the
strap lower points start on the same line. The fit report measures the bag body
separately from the torso strap and uses a signed inner-face clearance. Run
Blender build/bake and Godot import. Python tooling 87/87.
2026-11-01 17:58:47 +01:00
zwitschi bbf4693475 Narrow the sunglasses and fix portrait-type parsing
Move the hinges to +/-0.075 (0.035 box), arms to +/-0.098, and lenses to a
0.024 m radius while keeping the lens plane in front of the eyes. Rebuild the
GLB and AO; fit now measures 0.202 m total width (0.64x head). capture_realism
now parses the documented --portrait-type option instead of ignoring it.
Python tooling 87/87; headless 228/228.
2026-11-01 17:56:10 +01:00
zwitschi d7b0203502 Document commit and milestone steps in the agent workflow 2026-11-01 17:53:24 +01:00
zwitschi da876e3545 Add a gear-vs-body fit report to the Blender asset tool
`blender_assets.py fit` measures each character head width (skin vertices
weighted to Head in y 1.55-1.81) and clothing radius (cloth skirt in y 0.30-0.52,
pants hip fallback), then prints every attached head/back/chest gear bounding box
with head-band status and x/radial skirt clearance. The report confirms the
glasses width and shoulder-bag coat clip, giving the next fit fixes numbers.
Python tooling 87/87; asset audit clean.
2026-11-01 17:52:38 +01:00
zwitschi 1ee32215b0 Detail the NPC avatars and finish the realism follow-ups
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Blender library
- Rebuild the face: sclera, iris and upper lid per eye, brow ridge, cheekbones,
  nose bridge and tip, ears with lobes, split upper/lower lips.
- Give each hand four fingers plus a thumb and end every sleeve in a cuff ring.
- Raise CHAR_MAX_VERTS 4000 -> 5000 (16 avatars still cost ~6.9 ms of a 50 ms
  frame), decimate the body to 26 %; characters are now 3,974-5,026 tris and
  3,744-4,807 verts. Rebuild all 8 variants and re-bake their AO maps.

Capture tooling
- Add a `portrait` stage to capture_realism.gd: freeze one NPC, park the crowd
  out of frame, stand the subject on open sidewalk, turn it into the camera and
  put the guard on the sunlit side. New --portrait-type=N picks the role.
- Add the npc_portrait_day concept and map the shot to it, so avatar detail is
  scored against a portrait instead of a wide street frame.

Flashlight
- Drop the fake additive beam (Materials.light_beam + the cone mesh): on GL
  Compatibility a first-person camera inside the cone renders it as a filled
  disc with a bright far ring. Keep the tight spot (14 deg, 20 m, energy 4.2)
  and its headless test; the visible shaft is now planned behind a Forward+
  preset.

Docs and task tracking
- Move the finished realism follow-ups (ground exposure, flashlight, NPC detail,
  judge re-check) to DONE.md and expand TODO.md with step-by-step plans for NPC
  gear fit, venue geometry plus the committed gallery, and the Forward+ preset.
- Record the decisions, gotchas and portrait recipe in MEMORY.md; update the
  Blender contract, the concept README and the capture section of README.md.
- Refresh the gallery captures affected by the avatar rebuild.

Checks: headless 228/228, Python 86/86, blender_assets audit exit 0,
scene_concepts audit all GOOD, screen_designs compare 11 good / 0 bad,
smoke_npc_budget OK, boot clean (only the known headless mesh warning).
2026-11-01 17:46:25 +01:00
zwitschi 2624b54b23 Compact the plan and synchronize project documentation
- reduce PLAN.md from a 674-line implementation log to a 187-line product/design
  plan; keep escalation, venues, NPC/systems, controls, presentation and constraints
- refresh README for implemented venues, Blender/v2 assets, the custom test runner,
  Web target, current release state and the screenshot smoke
- update architecture for 0.6.0, the runtime art loader, Gitea CI/build/publish,
  Web deployment, six release artifacts and visual regression
- remove stale uncommitted/active-pass claims from MEMORY and point it at the
  current NPC follow-ups; move the completed documentation task to DONE
2026-11-01 17:37:02 +01:00
zwitschi 85d67ff8b1 Fix heading hierarchy and restore the future ideas section in PLAN.md 2026-11-01 17:33:15 +01:00
zwitschi 47c57ddc76 Tighten the flashlight spot and refresh captures
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 37s
Narrow the spot to 11° with a 20 m range so it lights the person/door rather
than the whole facade, and regenerate the staged screenshots. Headless 227/227,
compare 11 good / 0 bad.
2026-11-01 17:06:57 +01:00
zwitschi 3c07525351 Use a vertical fade for the flashlight beam
Full build / Import + headless tests (push) Successful in 37s
Full build / Full desktop build + optional release (push) Skipped
A GradientTexture1D varies along U, so the cone showed a bright ring; build a
1×64 vertical ramp for UV.y instead (faint at the lens, brightest near the
start, gone before the tip). Regenerate the affected captures; headless 227/227,
compare 11 good / 0 bad.
2026-11-01 17:06:09 +01:00
zwitschi 4a672025a2 Capture the late-night avenue and flashlight in the LATE phase
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped
Jump the staged shift to 760 s before the night shots, so 05/06 are true night
scenes with a visible flashlight beam instead of dusk. Regenerate the affected
captures; compare stays 11 good / 0 bad.
2026-11-01 17:05:06 +01:00
zwitschi 6acbc2b3a4 Tune the flashlight beam and refresh affected captures
Full build / Import + headless tests (push) Successful in 37s
Full build / Full desktop build + optional release (push) Skipped
Widen and lengthen the additive beam cone and raise its near-end opacity so the
night flashlight reads on screen. Regenerate the staged captures; compare stays
11 good / 0 bad, headless 227/227.
2026-11-01 17:04:09 +01:00
zwitschi d7e523f63c Adopt the realism pass and refresh screenshots/docs
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Skipped
- add the v2 runtime picks: per-phase skyline panoramas, brick, sidewalk
  pavers, dry/wet asphalt, awning canvas, and their normal maps
- add the realism tooling and evidence: tools/scene_concepts.py, capture_realism.gd,
  concept frames, reports/sheets, and tests
- retune phase lighting/fog/exposure, add sun yaw, brighten the night glow,
  add the flashlight beam mesh, and rebuild the planter/tree assets
- regenerate all eleven staged screenshots from the current build; compare is
  11 good / 0 bad
- finish the visual-alignment closeout in DONE/TODO, update architecture,
  blender-assets, PLAN, and ignore rules (editable Blender sources untracked)
- keep the judge retry and document the verified default judge model

Verification: headless 227/227; Python tooling 86/86; check_screenshots 11/11.
2026-11-01 17:03:05 +01:00
zwitschi 26ecb93fd4 Move the body-camera overlay below the shift status
The REC/clock block sat on top of the top-right shift/rep/crowd lines. Start it
at y=92 and slightly farther from the edge, keeping the overlay visible without
covering the status. Headless 226/226 and a windowed HUD screenshot checked.
2026-11-01 16:51:19 +01:00
zwitschi 8997f59819 Record the judge re-check and the lighting blocker
Mark the default vision judge as verified (7 match / 4 review / 0 diff), note the
active realism pass blocking the remaining lighting task, and replace the stale
memory note that said screen_designs still read the docs tree.
2026-11-01 16:50:27 +01:00
zwitschi 79270795a8 Finish the image-root task and untrack Blender sources
- ignore assets/models/blender_src/*.blend (keep the .gdignore marker tracked) and
  remove the 46 editable .blend files from the index; they remain on disk and are
  regenerated by tools/blender_assets.py
- move the completed storage/docs/code-path/art-loader/verification, mouse-fix,
  and pop-up sections to DONE.md; TODO now holds only the open lighting item,
  realism follow-ups, and optional ideas
- docs changes only; runtime code unchanged
2026-11-01 16:49:43 +01:00
zwitschi dc4bc6c511 Use the access-inspection mockup behind the inspection panel
Add the access_inspection art layer to the HUD, keep the panel transparent and
padded, center the title/status/issue text and artifact cards, and hide the art
with the panel. Also record the pop-up visual pass as done. Headless 226/226
verified in a clean HEAD worktree.
2026-11-01 16:46:25 +01:00
zwitschi c8b98fdb30 Recapture the mouse after selecting a venue
_start_shift() now calls _refresh_freeze(), clearing the start-menu input lock
and returning to captured mouse mode. The movement smoke asserts both and no
longer captures the mouse itself. Headless 226/226 and smoke_movement pass in a
clean worktree; only this game_manager hunk is staged, not the active realism
edits.
2026-11-01 16:45:05 +01:00
zwitschi d96f53c285 Add a repeatable screenshot capture and compare smoke
Both capture helpers now accept --out=res://docs/screenshots, and
tools/check_screenshots.sh renders the staged set (using xvfb-run when headless)
before running screen_designs.py compare. Record the judge result: 7 match,
4 review, 0 diff. Headless 226/226 and the smoke both pass in a clean worktree.
2026-11-01 16:43:10 +01:00
zwitschi b39fee6176 Match the radial menu to its mockup
Widen the doughnut, use dark charcoal wedges with a dimmed scene behind them,
add divider spokes and inner/outer rings, raise the center-name size, and use a
restrained light highlight for the selected slice instead of the old yellow
fill. Headless 226/226 verified in a clean HEAD worktree.
2026-11-01 16:39:28 +01:00
zwitschi c2c93c28a5 Record the HUD alignment and capture comparison result 2026-11-01 16:37:56 +01:00
zwitschi 3030c60ae1 Align the HUD target chip and escalation meter with the mockups
Move the target name out of the top-left stack into a boxed chip under the
crosshair, keep the log/hints left and shift status right, and add a five-segment
escalation bar under the existing color-coded label. Empty targets hide the
chip. Headless 226/226 verified in a clean HEAD worktree.
2026-11-01 16:36:06 +01:00
zwitschi cb65a5a601 Note the radio/wand/timeline art wiring in memory 2026-11-01 16:30:48 +01:00
zwitschi 06a4f5479f Align the body-camera timeline with its sidebar mockup
Draw body_camera_timeline behind a full-height narrow panel, constrain the
content to that column, wrap long event text, and scroll the event list so the
Previous/Next/Close row stays pinned at the bottom. Headless 226/226 verified in
a clean HEAD worktree.
2026-11-01 16:30:17 +01:00
zwitschi 8032999a4d Use the radio and wand mockups behind their forms
Draw radio_report/emergency_radio behind a transparent, padded form and switch
the backdrop with the call type. The wand HUD popup now shows wand_screening
behind its text while open. Both covers use the mockup panel bounds so baked-in
controls stay hidden. Headless 226/226 verified in a clean HEAD worktree.
2026-11-01 16:29:35 +01:00
zwitschi 8582e496ff Point the README at the runtime image root
Update the resize/audit examples and the screen-design section to
assets/images/ui|scenes, with docs/screens/designs as the reference snapshot.
The separate realism-pass README hunk stays unstaged.
2026-11-01 16:20:10 +01:00
zwitschi ae326ce970 Mark the aligned menu/panel items done in the visual pass 2026-11-01 16:20:01 +01:00
zwitschi 00faf45e83 Use the shift review mockup as the end-of-shift backdrop
Wrap the review content in a Control root, draw the shift_review art behind an
opaque panel, and style the action buttons to match the mockup. Headless
226/226 verified in a clean HEAD worktree.
2026-11-01 16:19:54 +01:00
zwitschi 41d3511fc7 Record the runtime art loader and the active realism workstream in memory 2026-11-01 16:19:23 +01:00
zwitschi 1efa5bc7d9 Use the pause and settings mockups as panel backdrops
Add UiArt.add_screen_backdrop() to place the reference art behind an opaque
panel, then wire the pause overlay and settings screen to it. Pause rows get
larger, rounded styles; settings widens to fit its expanded controls. Headless
226/226 verified in a clean HEAD worktree (the realism WIP currently editing
materials.gd remains unstaged).
2026-11-01 16:19:18 +01:00
zwitschi 4745adeb13 Move screen designs to the runtime root and add the UI art loader
- screen_designs.py now reads/writes assets/images/ui/ (ADR-0014); docs/
  screens/designs/ stays the reference-only snapshot, documented as historical
- add src/game/ui/art.gd: cached loader for assets/images/ui|scenes with null
  fallbacks for missing files, plus headless tests
- start menu uses the start_menu scene art as a full-screen backdrop, covers the
  baked-in mockup panel with an opaque panel, and styles the venue rows to match
- update README/architecture/TODO path notes and mark the storage/code-path/loader
  items done; DONE records the historical path move
- compare still 11 good / 0 bad; headless 226/226

README/PLAN realism-pass edits from another workstream remain unstaged." && git log --oneline -1
2026-11-01 16:00:12 +01:00
zwitschi 15deb58b73 Import the promoted runtime mockups
Run the headless Godot import after copying the 21 design references into
assets/images/ui/; all sidecars are generated and TODO records the item done.
2026-11-01 15:48:22 +01:00
zwitschi 6a0bf70fdf Document the asset storage convention
Add the path table from ADR-0014 to the architecture documentation (runtime UI/scene
art, tiled textures, models, reference art, QA captures, raw sources) and close the
second storage-restructure TODO item.
2026-11-01 15:47:56 +01:00
zwitschi f458551594 Claim TODO tasks with [-]; promote mockups to assets/images/ui
- AGENTS.md now requires agents to mark a task [-] while claimed/in progress and
  only use [x] after implementation and verification, with an example
- copied the 21 reference mockups from docs/screens/designs/ into the runtime
  root assets/images/ui/ (byte-identical names); docs/screens/designs/ stays as
  the reference-only copy per ADR-0014
- MEMORY records where the copies live and that screen_designs.py still reads
  the docs tree until the code-path migration

First storage-restructure TODO item completed.
2026-11-01 15:47:35 +01:00
zwitschi 8f228b0c56 Decide the canonical runtime image root (ADR-0014)
assets/images/ is now the runtime root for composed interface/scene art:
ui/ for screen and panel art, scenes/ for full-frame scene art. Tiled PBR
textures remain under assets/textures/; docs/screens/designs and concepts stay
reference-only; QA captures stay under docs/screenshots. Mockups that become
runtime art are copied/processed with a new name (no overwrites) and always
have a code fallback.

Records the convention in MEMORY, adds ADR-0014 to the architecture index, and
closes the first storage-restructure TODO item.
2026-11-01 15:16:24 +01:00
zwitschi d307746351 Close the v0.6.0 graphics plan and record the release
Full build / Import + headless tests (push) Successful in 32s
Full build / Full desktop build + optional release (push) Skipped
- TODO now tracks only the visual-alignment pass and optional future ideas; the
  completed Blender/graphics plan moved out
- DONE records the six published v0.6.0 artifacts and final verification counts
- header updated to v0.6.0 / 222 headless / 69 Python tests
2026-11-01 15:12:37 +01:00
zwitschi d872034388 Bake library AO and lift environment textures
Full build / Import + headless tests (push) Successful in 29s
Full build / Full desktop build + optional release (push) Successful in 3m43s
Phase 4 of the v0.6.0 graphics plan:

- tools/bake_ao.py bakes 256² AO maps for every Blender character/gear/prop
  (Cycles, existing UVs) next to each GLB; Materials.apply_ao() wires the red
  channel into character slots, gear, and props. Compatibility has no SSAO.
- Regenerated brick/concrete/asphalt at 1024² with OpenRouter, derived tangent
  normal maps via tools/make_normal_maps.py, and wired them through
  SurfaceMaterials.textured() without changing UV scales.
- Evaluated Forward+: boots on desktop Vulkan, but the renderer is project-wide
  and Web must stay GL Compatibility; v0.6.0 keeps Compatibility (decision in
  MEMORY/plan). A desktop-only override is deferred.
- Recaptured screenshots; compare 11 good / 0 bad. Fixed marquee text alignment.
- Tests: baked AO maps exist for all 46 assets; prop/character AO material
  checks; headless 222/222, Python 69/69, all smokes pass.
- Version 0.6.0; ADR-0013 accepted; README/architecture/blender-assets/plan and
  TODO/DONE updated.
2026-11-01 15:07:27 +01:00
zwitschi d8cfddbf89 Integrate the Blender character, gear and prop library
NpcAvatar
- select one of 8 library character variants by role/seed (with npc_base and the
  procedural rig as fallbacks), keep Rig/Head and the existing bone animation
- tint only skin/cloth/pants/accessory/hair; fixed detail materials keep their
  colour, npc_vest keeps its hi-vis vest, firefighter/guard use dark uniform tints
- attach caps, hoods, helmets, backpacks, SCBA tanks, lanyards and hand items at
  the documented head/back/chest/hand points (primitive fallbacks retained)

WorldBuilder
- prefer assets/models/blender/props/ for the six replacements and the new
  street dressing (street lamp/tree, palms, planters, A-frame, cone, barrier,
  stanchion, podium, handrail, wall lantern, double door, marquee, sedan)
- fit the same colliders/navigation obstacles and keep the placement validator;
  collect library emissives (bulbs, marquee face, wall lanterns, door LED, sedan
  lights) for set_night_mode()
- add a library triangle counter + 60k budget check (current ~27,986); toggle
  prop gallery to the library models; fix the marquee label placement

Tests/smoke
- character selection/gear attach/fixed-material tint tests; library prop load,
  emissive and triangle-budget tests; smoke_props routes a guest around a
  stanchion in open sidewalk (the old bench route is now crowded by trees)
- headless 222/222, Python 68/68, all smokes pass, compare 11 good / 0 bad

Docs: TODO Phase 3.5 moved to DONE, README/architecture/blender-assets/plan
describe the integrated runtime, MEMORY records the integration rules.
2026-11-01 14:57:02 +01:00
zwitschi d513e0b82c Stage the Blender character/gear/prop library
Commit the finished generator output from the parallel Blender pass:
8 skinned NPC variants, 18 gear pieces, and 20 props under
assets/models/blender/, editable sources (gdignored), manifest, review sheets,
and the integration notes in docs/blender-assets.md.

- audit: all 46 assets clean (characters 3,788-4,640 tris, under 6000/4000)
- tests: Python manifest/audit contract and Godot import/bone/slot/ground checks
- docs: README library section, architecture/plan references, TODO Phase 3.5
  status, MEMORY integration rules
- .gitignore: per-asset preview PNGs, extracted GLB textures, blend1 backups

Nothing in the game loads the library yet; integration follows." && git log --oneline -1
2026-11-01 14:33:30 +01:00
zwitschi 7efb5024ec Extend the v0.6.0 plan with the Blender asset library
Document the larger asset set produced by the running Blender generator
(tools/blender_assets.py): 8 NPC character variants, 18 gear pieces, and 22
street/venue props with a manifest, editable sources, and preview sheets.

- TODO gains Phase 3.5 with the audit/budget reconciliation (npc_casual is
  currently 7,756 tris / 5,616 verts, above the 6,000/4,000 contract), character
  selection by role, gear attach points, prop replacement/placement, emissive
  wiring, budgets, and screenshot gates
- PLAN's Visual Quality Direction describes the library, runtime selection, and
  offline fallbacks
- docs/plan-graphics-v0.6.md adds the library contract/output table and
  integration steps, and points Phase 4 at the Blender props
- MEMORY records the manifest-as-source-of-truth rule and the generator caveat

Generated library files stay untracked until the running build finishes and
passes the audit; the generator itself is committed so the plan is reproducible.
2026-11-01 13:39:03 +01:00
zwitschi a231f7588a Load and animate the Blender-authored NPC base mesh
Wire the skinned model from ADR-0013 into NpcAvatar:

- _build_skinned_body() instantiates the imported npc_base.glb, binds the 14
  armature bones, and keeps the procedural _build_body() as the offline
  fallback (use_skinned_model can force it in tests)
- outfit_for()/layer_for() tint the five material slots through the new
  SurfaceMaterials.npc_slot(); accessories and hand items attach under Rig/Head
- the existing limb pivots (Hip/Knee/Shoulder/Elbow/Hand + Head marker) keep the
  old animation math; _sync_skeleton() copies their rotations to bones. Bone
  pose rotation is absolute local orientation, so it is composed with the rest
  basis (otherwise the whole body snaps into a broken pose)
- skinned rigs scale uniformly (non-uniform parent scale shears the skin) and
  keep Rig/Head for the focus cone, nametag, and reaction tag
- commit the editable npc_base.blend plus a .gdignore: Godot's editor otherwise
  tries to import the blend and hangs during --import
- npc_pipeline audit tests (contract, textures, clean GLB, committed source)

Verification: headless 212/212, Python tooling 60/60, NPC budget 16 avatars =
144 meshes at ~6.9 ms, movement/focus/props smokes pass, compare 11 good / 0 bad.
TODO Phases 0-3 checked; environment lift and release remain.
2026-11-01 13:26:37 +01:00
zwitschi 3989d7b2e5 Rename Step Line to Gig Line
Update the game name in project.godot, export presets, UI/marquee/title strings, packaging (AppImage/NSIS/desktop/appdata), tools, docs, and the generated prop GLBs. Packaging assets and README screenshots are regenerated. Headless suite 209/209 and Python tooling tests pass.
2026-11-01 12:52:17 +01:00
zwitschi 0eb17e3bb4 Fix NPCs walking backwards
Avatars called Godot's look_at(), which points the node's -Z axis at the target,
but the rig's face (eyes/brows/mouth/focus disc, and the Blender successor) is on
+Z. Every walking NPC therefore faced away from its movement direction.

- add the pure yaw_towards(direction) helper (+Z forward) and use it in both
  movement paths and the reaction face-turn
- fix the head-turn offset to the same +Z convention (basis.z)
- test +Z/+X/-Z/arbitrary directions; recapture targeting screenshot
- TODO bug item moved to DONE, MEMORY records the convention

Headless 209/209.
2026-11-01 12:30:41 +01:00
zwitschi a7daae8a14 Add NPC backwards-walking bug to TODO 2026-11-01 12:26:35 +01:00
zwitschi 3f4a8e5f9f Regenerate screenshots; document NPC pipeline and confirmed assets
Ten of the eleven README captures are refreshed (the prop gallery is unchanged). README gains an NPC character-model subsection and a 'Confirmed game assets' section with linked, audited textures/models; architecture Level 2 lists npc_pipeline.py and asset_pipeline.py.
2026-11-01 12:25:35 +01:00
zwitschi 91b10e0f94 Document Godot project file format comments 2026-11-01 12:11:59 +01:00
zwitschi 4943dbff16 Extend AI image tooling with variants; drop dev asset folders from git
asset_pipeline generate gains --variants N (saved as <stem>-1..N.png). .gitignore now excludes assets/models/concepts, assets/models/npc, assets/textures/npc/ai, and raw assets/textures/ai outputs; only game_ready and the game-facing NPC maps/models stay tracked.
2026-11-01 12:08:34 +01:00
zwitschi b24d0aeb61 Plan the v0.6.0 graphics pass and add Blender-authored NPC assets 2026-11-01 12:02:27 +01:00
zwitschi a27e8cb74c Character and movement pass
Full build / Import + headless tests (push) Successful in 33s
Full build / Full desktop build + optional release (push) Successful in 3m37s
NPC characters
- Articulated rig (hips/knees, shoulders/elbows/hands) replaces rigid capsules;
  the walk cycle bends knees and elbows and flees with a longer stride.
- Layered outfits (jacket/vest/coat/dress), extended accessories and hand items
  (bag, umbrella, phone, camera, clipboard, cup), face details and five hair
  silhouettes, all deterministic per profile seed.
- Idle behaviour layers (phone, clipboard, crossed arms, look-around), breathing
  and weight shift; escalation raises hands or points, and noticed NPCs turn
  toward the guard.
- Reaction symbols above the head (!!, ?!, !, ?), door-queue offsets assigned by
  GameManager, three LOD tiers with hysteresis and a billboard impostor.
- smoke_npc_budget.gd: 16 avatars = 534 meshes, ~6.9 ms average (50 ms budget).

Player movement
- Separate accel/decel, step-up assist for kerbs and the entrance steps, and a
  forward-axis fix: W was inverted (get_axis(forward, back) is negative).
- Head bob, landing dip and precision-walk FOV, gated by persisted head-bob and
  invert-Y settings; precision chip and tick; surface-aware footsteps and a
  landing cue forwarded to AudioFeedback.
- Gamepad support (sticks, deadzone, face buttons, bumpers), a control legend
  that follows the active device, and named actions for the remaining keys.
- Key/button remapping in Settings, persisted as input_bindings and applied by
  InputBindings (mouse and gamepad axes are never removed).
- smoke_movement.gd (windowed): climbs the steps, checks precision speed, chip,
  footsteps and landing; new 11-precision-walk capture and catalogue entry.

Verification: headless 208/208, Python tooling 56/56, all 11 smokes pass,
compare 11 good / 0 bad, movement judge match. Version 0.5.0. Only optional
Future ideas remain in TODO.
2026-11-01 10:41:17 +01:00
zwitschi 2a4e0d1998 Audit prop budgets and add a prop-gallery art check
- audit_glb decodes geometry: 400 tris / 600 verts budget, degenerate
  triangles, UV range, ground contact, one material; newspaper box parts moved
  to sit on y=0
- capture_prop_gallery.gd renders the six props on a studio backdrop; new
  prop_gallery catalogue entry + AI mockup (compare 10 good, judge match 0.90)
- headless test that instances share the imported atlas material
- README documents the gallery capture and budget; TODO props pipeline section
  moved to DONE
2026-11-01 10:27:27 +01:00
zwitschi bb40a98537 Give street props colliders, zoned placements and variants
Full build / Full desktop build + optional release (push) Successful in 4m2s
Full build / Import + headless tests (push) Successful in 31s
Props were decoration: the guard and guests walked straight through benches,
bins, bollards and planters. They now block movement and steer guests around.

Collision
- _add_prop_model() fits a StaticBody3D from the model's mesh bounds in
  holder-local space: a cylinder for bollard/bin/hydrant, a box otherwise, so
  placement rotation and variant scale carry over.
- Fallback primitives go through the same fit, so a missing .glb cannot silently
  remove collision.
- Each prop also publishes a NavigationObstacle3D; NpcAvatar enables agent
  avoidance and moves with the safe velocity from velocity_computed. The navmesh
  stays a flat walkable plane and avoidance does the steering.

Placement
- PROP_PLACEMENTS groups furniture by entrance, mid-block and curb zones, with a
  static placement_problems() validator for bounds, overlap/min-spacing,
  duplicates, and the door/steps/guest-lane exclusions.
- variant_for(index) gives deterministic yaw jitter, subtle scale, and optional
  clutter (bag by the bin, crate by the newspaper boxes, cone by the hydrant).

Verification
- New windowed smoke_props.gd: guard walks into a bench and stops; a guest
  headed beyond the bench routes around it and never enters the collider.
- Headless 182 -> 187: placement validation and its failure modes, variant
  determinism/range, collider + obstacle on every placed prop, fallback collider.
- Other smokes (PLAN verbs, crowd, emergency, timeline) still pass; Python
  tooling 53/53; screenshots recaptured; compare 9 good / 0 bad.
- Version 0.4.0. TODO: collision/placement section moved to DONE; prop pipeline
  checks remain.
2026-11-01 10:16:53 +01:00
zwitschi a3e90accdd Plan the presentation pass: props, NPCs, movement
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 2m12s
Replace the flat "future ideas" list with three scheduled sections and concrete,
reviewable checkboxes (41 tasks):

- Better Props: colliders for the generated models (they have none today), a
  placement table with variants, sign/bus-stop/shelter/street-lamp props, a
  close-range detail layer, pipeline audits, and a prop-gallery capture.
- Better NPC Characters: articulated rig, layered outfits and accessories, face
  and hair variety, idle/conversation animation, door-queue staging, reaction
  readability, LOD tiers, and a re-measured NPC budget.
- Better Player Movement: accel/decel split, step-up assist, head bob/FOV and a
  motion-sickness toggle, precision-walk feedback, surface footsteps, gamepad
  support, input remapping, and movement tests.

Kept live 3D replay, extra venues, voice audio, and the image-to-mesh swap in
Future ideas. Memory notes the grounding facts (props have no colliders, rigid
NPC rig, single accel value). Docs-only: no version bump or release.
2026-11-01 10:05:04 +01:00
zwitschi 3025ba1cef Capture the wand panel, escalation HUD and body-camera timeline
Full build / Import + headless tests (push) Successful in 35s
Full build / Full desktop build + optional release (push) Successful in 3m56s
The three newest catalogue screens had AI mockups but no staged capture, so
compare and judge skipped them. capture_screenshots.gd now continues past the
night set:

- switches to The Joke Shop at 620 s (EVENING still has routine spawns), frames a
  guest, and retries the wand until it alerts; if nobody carries an alerting item
  it stages the documented keys false-alarm case.
- applies a formal warning (arms the body camera) then blocking, jumps to 760 s
  for night lighting, and captures the HUD at L3.
- closes the open incident and opens the body-camera timeline, which now shows
  the warning / body-camera / blocking rows.

Wired the refs for wand_screening, escalation_hud and body_camera_timeline, and
regenerated the escalation mockup as a third-person over-the-shoulder night shot
so the reference matches the game camera. Added Hud.clear_log() to drop
target-cycling noise from staged shots. README gallery lists the three captures.

Verification: headless 182/182, Python tooling 53/53, compare 9 good / 0 bad,
judge: wand match 0.90, timeline match 1.00, escalation review, 0 diff.
Version 0.3.3.
2026-11-01 00:29:58 +01:00
zwitschi a5e7b3c013 Add AI mockups for the seven remaining catalogue screens
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Successful in 4m4s
The design catalogue grew to 19 screens, but only the original twelve had an AI
reference; the rest still showed "design -" in `list`. Generated the missing set
so every screen can be reviewed against an intended look:

- wand screening (metal-detected panel with carried items and allow/deny)
- Joke Shop queue, TV Studio arrival, crowd panic, responder arrival
- body-camera timeline, escalation HUD

`generate` now loads `.env` the same way `judge` does, so `--ai` no longer needs
the API key exported by hand. Each reference is 1280x720; the twelve existing
mockups were kept (no `--force`).

Verification: `list` reports 19 designs, Python tooling 53/53, headless suite
182/182, `compare` still 6 good / 0 bad on the captured screens. Version 0.3.2.
2026-11-01 00:01:02 +01:00
zwitschi f2863abeac Draw prop models with a single atlas material
Full build / Import + headless tests (push) Successful in 33s
Full build / Full desktop build + optional release (push) Successful in 3m39s
Each generated prop used one material per part (body, accent, metal...). The
model pipeline now packs a prop's palette into one power-of-two colour atlas plus a
matching metallic/roughness atlas and emits a single material:

- atlas_for(): 8x8 cells per palette role, colours in sRGB, roughness in the G and
  metallic in the B channel of the second atlas; NEAREST sampling keeps cells flat.
- build_glb(): every part merges into one primitive with constant per-vertex atlas
  UVs; the material uses baseColorTexture + metallicRoughnessTexture with factors 1.0.
- audit_glb(): fails unless a prop has exactly one material, both atlas textures,
  two images, and TEXCOORD_0 on every primitive.
- Removed the now-unused sRGB->linear factor helpers; colour travels in the texture.

Verification: headless 181 -> 182 (new "one material per prop scene" test), Python
tooling 53/53 (atlas determinism/size/UV/palette-override tests replace the old
factor tests), model audit OK for all six props, screenshots regenerated and
screen_designs.py compare still 6 good / 0 bad. Version 0.3.1.
2026-10-31 23:47:15 +01:00
zwitschi 7f92b8861d List the Web build in the README downloads table
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 25s
The v0.3.0 release now carries step-line-web-0.3.0.zip (37.5 MB, 63 MB unzipped);
downloads anonymously and serves from any static host. Docs only.
2026-10-31 23:13:43 +01:00
zwitschi afa1d6beec v0.3.0: rendering pass, generated props, selection model and Web build
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Successful in 4m17s
Rendering and presentation
- Shared surface-material module (src/game/materials.gd) with per-surface
  roughness/metallic/uv profiles; ACES tonemapping, glow, fog and phase-driven sky
  energy. WorldBuilder rebuilt on those profiles with facade windows that light up,
  door trim/handles, curb, lane dashes, bollards, bench, bin and planter foliage.
- NPC silhouettes: per-role clothing palettes, caps/hoods/backpacks/sunglasses,
  hands and shoes, arm/leg walk swing.
- Six generated low-poly GLB props (bollards, bench, bin, planter, newspaper box,
  hydrant) built by tools/model_pipeline.py, loaded by WorldBuilder with a
  procedural fallback; ADR-0012.

Selection and movement
- Focus ranking by view angle then distance (NpcFocus.rank_indices); Tab and the
  mouse wheel cycle nearby people initially, right click clears focus with a
  re-acquire grace, HUD shows the candidate count, Shift walks at half speed.
  ADR-0011. Windowed smoke extended to cover wheel cycling and clearing.

QA tooling
- screen_designs.py catalogue grown to 19 screens (8 scene, 11 UI) plus a `list`
  subcommand; draft generation keeps existing AI references unless --force.

Web export
- New Web preset using the non-threaded Godot template (static hosting, no
  COOP/COEP). tools/build_releases.sh exports build/web and packages
  build/step-line-web-<ver>.zip; publish_release.sh uploads it with the desktop
  artifacts. test_web_export.gd covers the preset and release wiring.

Verification
- Headless suite 177 -> 181, Python tooling 53 tests, all eight smokes pass
  (including the windowed focus/wheel smoke). Web bundle serves index.html,
  index.wasm (35 MB) and index.pck (30 MB) with HTTP 200 from a static server.
- Version 0.3.0; README, PLAN, RELEASING, TODO/DONE and MEMORY updated.
2026-10-31 23:07:41 +01:00
zwitschi f763cff744 Export venues as editor-authorable .tres resources
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Successful in 3m51s
- tools/export_venues.gd (SceneTree script) saves the three factory scenarios to
  content/venues/<id>.tres via ResourceSaver. The factory stays the source of truth;
  the exported files are what an editor workflow would edit and ship.
- New tests load the files back and check that id, name, event pool, profiles,
  policy (ID/wand), equipment, door-staff lines, shift phases, and locations all
  survive. They also fail when the exported files are missing or stale, so the
  export is re-run after content changes.
- GUT is consciously not adopted: the custom headless runner already covers 157
  assertions without an editor plugin and keeps CI dependency-free. Recorded in
  DONE.md.
- TODO: the planned backlog is complete; only optional future ideas remain.
- README/MEMORY updated. Version 0.2.16.
2026-10-31 22:16:55 +01:00
zwitschi 72b9378e69 Add a vision-model screenshot judge to the QA tooling
Full build / Import + headless tests (push) Successful in 32s
Full build / Full desktop build + optional release (push) Successful in 3m30s
`tools/screen_designs.py judge` sends each capture together with its concept
mockup to a vision model and asks whether the capture realises the same screen and
layout intent. This covers what pixel/perceptual metrics cannot judge.

- Prompt explicitly ignores art style, fidelity, fonts, and placeholder text, since
  the references are stylised mockups.
- Verdicts are classed match / review (mismatch below --fail-confidence, default
  0.6) / diff (confident mismatch, exits non-zero) / unknown; --json, --only,
  --dry-run, --model supported.
- Reads the repo .env for OPENROUTER_API_KEY without overriding real env vars.
- Default judge model google/gemini-2.5-flash-lite; openai/gpt-4o-mini verified too.

Tests/docs
- 7 new offline tests in tools/test_screen_designs.py (request payload embeds both
  images, JSON/fenced/parts/non-JSON parsing, threshold classification, .env
  loading); 30 Python tests pass.
- Live run: 3 captures match, 3 review, 0 confident diffs.
- README, TODO section moved to DONE, MEMORY. Version 0.2.15.
2026-10-31 22:11:45 +01:00
zwitschi 74f14014b2 Enable anonymous downloads on the host and harden Strings.fmt
Full build / Import + headless tests (push) Successful in 33s
Full build / Full desktop build + optional release (push) Successful in 3m31s
Host (applied with root@192.168.88.30)
- [service] REQUIRE_SIGNIN_VIEW = false in /etc/gitea/app.ini (backup written
  alongside), Gitea restarted. Verified anonymously: repo page 200, API 200, and a
  release asset downloads at full size (50.8 MB AppImage).

Localization safety (Open Polish)
- Strings.placeholder_count() counts conversions and ignores %% escapes.
- Strings.fmt() validates the count and fails safe (warns, returns the template)
  instead of raising an engine error when a key's placeholders drift.
- Strings.validate() lints the table for a trailing %, unsupported conversions, and
  mixed positional/sequential placeholders.
- Tests cover the counter, the fail-safe path, a clean table, and a formatting pass
  over every placeholder key with type-correct arguments.

Docs
- TODO: distribution, release-pipeline, and open-polish sections are complete and
  moved to DONE; the v0.2.4-v0.2.8 backfill is closed as superseded.
- README/MEMORY updated. Version 0.2.14.
2026-10-31 22:02:24 +01:00
zwitschi a2a0ded527 Author the missing scenario content from the PLAN lists
Full build / Full desktop build + optional release (push) Successful in 3m26s
Full build / Import + headless tests (push) Successful in 35s
Joke Shop
- Reservation list without ID (guest list valid, ID missing) — denied.
- Borrowed/mismatched ID (ID valid but mismatched) — denied.
- Guest who refuses the wand screen: new `refuses_screening` event flag, copied
  onto NPCController, handled in _toggle_wand_screening (logs wand.refused, records
  a wand_refused event, small prop hit, panel stays closed).
- Late arrival during the performance (ID + ticket, "arrive" intent).

Studio
- Filming-boundary event and a later repeat, both using the new `film_boundary`
  intent which maps to `steps_end` so the influencer stands past the posted line.
- Celebrity arrival now pulls in a small crowd via a new `extra_spawns` event field
  (two tourists + an influencer), spawned through the normal path.

Building
- Filming-boundary event whose hint points at the body camera and request-reason
  verbs, plus an unclear-intent event that hints at asking for a reason.

Engine support
- Events may carry `hint` (string key logged on spawn), `extra_spawns`, and
  `refuses_screening`; 9 new localized hint strings.

Tests/docs
- Headless suite 139 -> 149 (test_scenario_content.gd) and a new
  smoke_scenario_content.gd that spawns the refusal and celebrity events through the
  real signal path.
- README, TODO section moved to DONE, MEMORY. Version 0.2.13.
2026-10-31 21:55:24 +01:00
zwitschi 922a990c7a Record verified CI publishing and host maintenance
Full build / Import + headless tests (push) Successful in 24s
Full build / Full desktop build + optional release (push) Skipped
- v0.2.12 was tagged and CI produced the release with all five artifacts by itself,
  confirming the checkout-credential fallback end to end.
- Host maintenance through root@192.168.88.30: apt cache cleaned and journald capped
  at 300M (journal ~133M now), ~7.6G free of 16G.
- TODO release-pipeline section is fully closed; MEMORY records the host state.
2026-10-31 21:51:41 +01:00
zwitschi f0cec192a7 Restore pruned releases and verify CI publishing with v0.2.12
Full build / Full desktop build + optional release (push) Successful in 3m29s
Full build / Import + headless tests (push) Successful in 32s
- Host root filesystem was expanded from 7.8G to 16G (~7.8G free), so the disk
  blocker is gone.
- Re-published v0.2.0 (4 artifacts) and v0.2.9 (5 artifacts) from the local build/
  outputs; both releases verify.
- Root SSH from the workstation is not available (publickey denied, password auth
  closes the banner); the host is administered via the zwitschi login.
- Version 0.2.12 exists to verify the tag pipeline end to end: the workflow should
  create the release via the checkout-credential fallback and upload all five
  artifacts.
2026-10-31 21:46:09 +01:00
zwitschi 2dba562c52 Publish v0.2.11 and document the host disk limit
Full build / Import + headless tests (push) Successful in 26s
Full build / Full desktop build + optional release (push) Skipped
- Pruned the superseded v0.2.0 and v0.2.9 releases through the API (~565 MB of
  assets) after the host root filesystem hit 100%, which blocked release uploads
  (HTTP 500 "no space left on device") and even git push ("unpacker error").
- Published v0.2.11 locally with the fixed script: all five artifacts verified on
  the release (Linux/Windows/macOS ports, NSIS installer, AppImage).
- TODO records the remaining ~150 MB headroom and the exact host cleanup commands;
  MEMORY and RELEASING describe the disk failure mode and the pruning.
2026-10-31 21:39:15 +01:00
zwitschi afd086ba11 Fail loudly when release uploads do not land
Full build / Import + headless tests (push) Successful in 35s
Full build / Full desktop build + optional release (push) Skipped
Follow-up to the publishing fix: CI created the v0.2.11 release and logged each
upload, but the assets never arrived, so the job looked successful while the
release was empty. The host root filesystem is full (7.8G, 100%), and Gitea
answered HTTP 500 "Attachments.Save: no space left on device".

- publish_release.sh now checks the HTTP status of every asset upload and prints the
  response body on failure, then verifies the release reports as many assets as were
  sent. Empty releases can no longer pass silently.
- Deleted the empty v0.2.11 release so the "latest" release (v0.2.10) still has all
  five artifacts; the tag itself remains.
- TODO/MEMORY record the host disk blocker with concrete cleanup commands and note
  that v0.2.11 artifacts are built locally in build/ for upload once space exists.
2026-10-31 21:38:12 +01:00
zwitschi b4d189c325 Publish releases with the runner's checkout credentials
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Successful in 3m37s
The v0.2.10 tag build still failed to publish: its env dump showed no token at all,
because this Gitea/act runner does not provide `github.token`, and the repo has no
GITEA_TOKEN secret.

publish_release.sh now resolves credentials in order: GITEA_TOKEN,
GITEA_AUTH_HEADER, ~/.git-credentials, then the local git extraheader that
actions/checkout writes for the job. That header carries the runner's basic-auth
credentials for the same server, so tag builds publish with no repo secret.

Verified by adding the extraheader locally and publishing v0.2.10 (all five
artifacts: Linux/Windows/macOS ports, NSIS installer, AppImage), then removing it.

Docs: RELEASING describes the four credential sources and warns that github.token is
not available here; MEMORY records both failed attempts and the final fix; TODO keeps
the "verify on the next tag" follow-up. Version 0.2.11.
2026-10-31 21:30:24 +01:00
zwitschi 6684d36739 Fix release publishing and add a CI token fallback
Full build / Import + headless tests (push) Successful in 26s
Full build / Full desktop build + optional release (push) Failing after 3m58s
Tag builds since v0.2.2 built all artifacts but never published a release:
- tools/publish_release.sh only assembled AUTH in the GITEA_TOKEN branch; the
  ~/.git-credentials branch set CRED and then used an unbound ${AUTH[@]} under
  `set -u`, so every API call failed and the job ended "could not create or find
  release". It now sets AUTH=(-u "$CRED").
- The repo has no GITEA_TOKEN secret and the runner has no git credentials, so the
  workflow now falls back to the built-in workflow token:
  GITEA_TOKEN: ${{ secrets.GITEA_TOKEN || github.token }}.

v0.2.9 was then built and published locally with all five artifacts (portable
Linux/Windows/macOS, NSIS installer, AppImage) and the release page verified via
the API. v0.2.4-v0.2.8 remain unpublished; they are superseded by the latest.

Docs
- docs/RELEASING.md records the incident and the local-publish fallback.
- TODO gains a release-pipeline follow-up (verify CI on the next tag).
- MEMORY notes the root cause, the missing secret, and which releases exist.
- Version 0.2.10.
2026-10-31 21:23:09 +01:00
zwitschi 3f4ac7e9aa Add accessibility pass: escalation meter and working HUD font scale
Full build / Import + headless tests (push) Successful in 33s
Full build / Full desktop build + optional release (push) Failing after 3m31s
- Escalation cue now includes a shape meter and numeric level
  ("Escalation: Blocking [###--] L3/5") in addition to the colour and name, so it
  reads in grayscale. Hud.escalation_meter(level) is pure and tested.
- HUD font scale from settings actually works: set_font_scale() resizes fonts via
  theme overrides instead of Control.scale, which pushed the right-anchored clock
  label off-screen. It now covers the top, log, target, hint, time, mode,
  escalation, cooldown, and debug labels.
- Settings load before the HUD exists, so _ready() re-applies player settings after
  _build_ui(); previously a saved font scale was ignored until the next apply.

Tests/docs
- Headless suite 134 -> 139 (test_accessibility.gd).
- Verified visually at 1.4x scale: no overflow, meter readable.
- README, TODO section moved to DONE, MEMORY. Version 0.2.9.
2026-10-31 21:17:42 +01:00
zwitschi 294ac17333 Add procedural crowd ambience and radio crackle
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Failing after 3m53s
- AudioFeedback.start_crowd_bed() builds a 4 s looping AudioStreamWAV at runtime
  (one-pole filtered noise plus a 68/103 Hz drone, seeded) and set_crowd_level()
  maps crowd mood 0..1 to -34..-12 dB, so the street gets louder as it gets
  agitated or panics. The bed starts with the shift and stops at shift end.
- play_radio_open()/play_radio_close() push short filtered-noise bursts; wired to
  opening the radio panel, submitting a report, cancelling, and closing the panel.
- Still asset-free and headless-safe: with a dummy audio server the calls no-op.
  _free_player() returns null before _ready and callers handle it.

Tests/docs
- Headless suite 127 -> 134 (test_audio_feedback.gd).
- New smoke_audio.gd: bed playing, level follows mood events, radio crackles once
  per open/close, bed stops at shift end. Verified windowed (real audio server).
- README, TODO section moved to DONE, MEMORY. Version 0.2.8.
2026-10-31 21:08:36 +01:00
zwitschi e2c2a0bf4c Add NPC theater poses: sit, photo, film, perform
Full build / Import + headless tests (push) Successful in 29s
Full build / Full desktop build + optional release (push) Failing after 3m44s
Once an NPC reaches its intent waypoint it now does something instead of standing
in an idle T-pose:

- sit: the rig settles 0.34 m into a seat and the legs swing forward on the steps
- photo/film: influencers and tourists raise their camera prop briefly (a sine
  pulse, so it reads as taking a shot rather than a permanently raised arm)
- perform: performers sway the sign board they carry

Pose selection lives in NpcAvatar.theater_pose() (intent + arrived); the math is in
pure helpers (crouch_for_pose, prop_lift_for_pose, sway_for_pose) so it is
headless-testable. Prop/leg nodes are captured at build time and animated from
their rest offsets. Performers also get a hand-held board and post.

Tests/docs
- Headless suite 120 -> 127 (test_npc_theater.gd).
- Verified visually with a staged sit/photo/perform lineup in front of the entrance.
- README, TODO section moved to DONE, MEMORY. Version 0.2.7.
2026-10-31 21:05:59 +01:00
zwitschi 1b976bf351 Add body-camera timeline to the shift review
Full build / Import + headless tests (push) Successful in 30s
Full build / Full desktop build + optional release (push) Failing after 3m38s
- New src/game/ui/event_timeline.gd flattens every recorded incident event
  (ADR-0005) into one time-ordered list and shows time, person, and a one-line
  summary. Step with Left/Right or the buttons; Esc returns to the review. No live
  3D replay in this first version.
- Shift review gains a "Body camera timeline" button that hands the review to the
  timeline; the review dim is hidden while it is open (stacked overlays made the
  screen nearly black) and restored on close.
- ShiftCoordinator records started_at_ms so the review can stamp events as mm:ss.
- 8 new string keys for the timeline UI and the review button.

Tests/docs
- Headless suite 113 -> 120 (test_event_timeline.gd), new smoke_timeline.gd that
  opens the timeline from the review, steps through every event, and closes back.
- README, TODO section moved to DONE, MEMORY notes. Version 0.2.6.
2026-10-31 21:02:25 +01:00
zwitschi 921bab796e Add emergency aftermath: evacuation, investigation, review copy
Full build / Import + headless tests (push) Successful in 26s
Full build / Full desktop build + optional release (push) Failing after 4m20s
- Responder outcomes now reach the shift review
  (ShiftCoordinator.responder_outcomes), and the review screen names them.
- "evacuation" (emergency call, low danger, no weapon) actually clears the entrance:
  the door is locked, every NPC flees and is marked as leaving, and a
  security_failure reputation hit applies.
- An emergency call the situation did not justify (no weapon, danger <= 1) appends
  an investigation note, records investigation_opened, and applies the existing
  false_report hit. The review lists the investigations.
- Fix dispatch dying after the first shift: coordinator.setup() builds a new
  RadioSystem, so the responder_arrived connection made in _ready was lost on every
  venue switch/restart. _connect_radio() re-wires it beside _connect_crowd().

Tests/docs
- Headless suite 105 -> 113 (test_emergency_aftermath.gd), new smoke_emergency.gd
  covering the investigation, evacuation aftermath, and review copy.
- README controls/status/smoke checklist, TODO section moved to DONE, MEMORY notes.
- Version 0.2.5.
2026-10-31 20:59:06 +01:00
zwitschi 71b0b53705 Add crowd presentation, fix NPC navigation, refresh screenshots
Full build / Import + headless tests (push) Successful in 27s
Full build / Full desktop build + optional release (push) Failing after 3m37s
NPC navigation (previously dead in the live game)
- Only assign NavigationAgent3D.target_position when it changes: re-assigning every
  frame invalidated the path, so get_next_path_position() kept returning the current
  point and avatars never moved.
- Follow the first path point that is closer to the destination than the avatars are.
  Nav points sit on the navmesh plane (y=0) while bodies stand at y~0.5, so the old
  3D distance test always looked "already there"; path[0] is also the old start point,
  so a naive "far enough away" pick steered backwards and oscillated.
- Fix fsm.state -> fsm.current_state in the idle-lifetime check.

Crowd presentation
- Aggressive behavior, visible weapons, and responder arrivals make idle bystanders
  stop and face the incident for 12 s; someone waiting at the entrance keeps focus.
- Panic (mood >= 0.75) walks people away from the door at 2.4 m/s, then they despawn.
- Wire reactions to EventBus.crowd_changed: CrowdSystem.apply_event() emits on the bus,
  not on its own `changed` signal (the first wiring received nothing).
- Measured a 16-NPC entrance at ~1.2 ms/frame (824 fps, vsync off), so no LOD/culling.

World and HUD
- Skyline is now a ring of four alpha-scissored planes at 200 m instead of a single
  plane; PlaneMesh defaults to horizontal, which is why the backdrop rendered as a band
  overhead. Larger ground plane, mipmapped texture, scissor threshold 0.5 (0.3 left
  mip dashes in the sky), lifted albedo so the city is not pure black.
- Flashlight aims straight ahead and lights the person/door instead of the ground.
- Shorter HUD controls line so it stops overlapping itself.

Tests, tooling, docs
- Headless suite 98 -> 105 (new test_crowd_presentation.gd, 7 tests).
- New smoke_crowd.gd: rubbernecking, panic, and flee-target sanity.
- capture_screenshots.gd starts a shift from the venue menu and stages the night set
  properly; all six README screenshots regenerated.
- README, TODO/DONE (crowd section moved), MEMORY updated. Version 0.2.4.
2026-10-31 20:55:10 +01:00
zwitschi f5ad1642b5 Add PLAN verbs and fix dead incident recording
Full build / Import + headless tests (push) Successful in 26s
Full build / Full desktop build + optional release (push) Failing after 3m31s
Verbs (PLAN levels 1-3)
- Eye contact: own Level 1 radial slice, logged as "eye contact".
- Recognition is a player decision: "Recognize" sets NPCController.player_recognized,
  writes access evidence, and GameManager only then appends a RECOGNITION artifact.
  It is no longer implied by NPC type.
- Body camera: real flag (off by default) driving the REC overlay; B toggles it and
  a formal warning arms it automatically, writing a body_camera incident event.
- Blocking stations the guard 0.9 m from the person toward the entrance and turns
  to face them, instead of only emitting the escalation level.
- Request reason: Level 3 ask answered from the NPC's seeded dialogue_lines.
- Radial grows to 12 slices (174 px outer radius, centered labels).

Incident recording fix
- IncidentRecorder.start_incident() was never called, so add_event /
  add_access_evidence / add_escalation silently no-opped and the shift review
  always reported zero incidents. Incidents now open on focus (guarded by
  has_open_incident), verbs call _ensure_incident, and ShiftCoordinator closes
  leftovers as "unresolved" before building the review.

Copy and docs
- 9 new string keys (eye contact, body camera, recognition, reason, station).
- README controls, 26-step smoke checklist, test count, status lists.
- TODO section moved to DONE; MEMORY records the verb design and the recorder bug.

Verification: headless 98/98, smoke_plan_verbs.gd + smoke_session_shell.gd +
windowed smoke_focus_radial.gd all pass. Version bumped to 0.2.3.
2026-10-31 20:29:50 +01:00
zwitschi 028f608298 Bump version to 0.2.2 for the session-shell release
Full build / Import + headless tests (push) Successful in 26s
Full build / Full desktop build + optional release (push) Failing after 3m42s
config/version, README latest-release note, TODO/MEMORY status refreshed for the
v0.2.2 tag. Headless suite 88/88.
2026-10-31 20:21:54 +01:00
zwitschi be429cab29 Add session shell and close world presentation follow-ups
Full build / Import + headless tests (push) Successful in 24s
Full build / Full desktop build + optional release (push) Skipped
Session shell
- Startup venue menu (src/game/ui/start_menu.gd): keyboard + mouse, replaces
  the live-shift boot and V as the player-facing venue path (V stays a debug bind).
- Pause overlay on Esc (src/game/ui/pause_overlay.gd): resume, restart shift,
  settings, quit to menu.
- Settings panel (src/game/ui/settings_panel.gd): mouse look sensitivity and
  HUD font scale, persisted via Save into user://settings.json and loaded on boot.
- Shift review "Run another shift" now returns to the venue menu.
- New smoke: src/test/smoke_session_shell.gd (menu -> shift -> pause -> settings
  -> menu), passes headless and windowed.

World and presentation
- Skyline backdrop restored with a transparent asset and an alpha-scissor
  unshaded material; fixes the opaque black slab from the earlier attempt and
  the unimported texture path.
- Steps and landing are a StaticBody3D with per-tread colliders, so the guard
  and NPCs walk up instead of clipping through.
- Marquee Label3D scaled up (72 -> 160) so STEP LINE reads from the sidewalk.
- Focus torus and name tag enlarged for 2-3 m readability.
- Routine traffic despawns after an idle lifetime (NpcAvatar.ephemeral), so the
  16-NPC cap no longer fills with standers; authored events stay for the shift.

Docs and tests
- PLAN.md test/CI section corrected to the custom headless runner and Gitea Actions.
- README controls table, smoke checklist, test count, and status lists refreshed.
- TODO/DONE/MEMORY updated; completed sections moved to DONE.md.
- Headless suite 77 -> 88 passing (settings round-trip, step colliders, skyline
  alpha, session-shell UI behavior, traffic lifetime).
2026-10-31 20:19:22 +01:00
zwitschi f11b441c07 Implement control pass: focus cone, action wheel, named inputs, boot fixes
Full build / Import + headless tests (push) Successful in 22s
Full build / Full desktop build + optional release (push) Skipped
- Venue-correct access artifacts (AccessLoadout) and screened-aware verify
- Cone focus on NPC heads with billboarded ring + display name (ADR-0008)
- Action wheel with de-escalation-first slices (ADR-0009); Esc/RMB cancel
- Procedural NPC face/hair/neck/shoes (ADR-0010); named input actions
- Fix GameManager parse error (radial typed as ActionRadial)
- Fix first-person camera height and guard spawning inside the building
- Move building volume back so the door/awning/marquee are visible
- Pull the event scheduler once per simulated second; cap live NPCs
- Regenerate README screenshots; update TODO/DONE/MEMORY/README; 77 tests

Adds src/test/smoke_focus_radial.gd for the windowed focus/radial smoke.
2026-10-31 15:58:01 +01:00
zwitschi 69484866db Add new game assets and update asset paths in code 2026-10-31 15:20:08 +01:00
zwitschi 53e98cf3fe Update documentation and regenerate screenshots; enhance asset integration details 2026-10-31 15:19:58 +01:00
zwitschi 8cf7745c80 Plan remaining gaps in TODO.md after a PLAN/code audit
Control pass (ADRs 0007-0010) stays first. Added the holes the audit found:
venue-correct access artifacts (override_access unread), session shell,
leftover PLAN verbs, crowd rubberneck, emergency aftermath, replay, NPC
poses, real ambience, a few Joke Shop/Studio events, and doc drift.
Host sign-in remains deferred. Generated-asset integration stays out
(already in DONE).
2026-10-31 15:17:02 +01:00
zwitschi 2b631ae6d9 Add ADRs 0007–0010 for locomotion, focus ring, radial menu, NPC presentation
- 0007: input layers so WASD/look never fire person-verbs; freeze while modal
- 0008: cone focus + billboarded head ring; no type names in the world
- 0009: doughnut action menu (LMB/Space open, click or A/D then Space, Esc/RMB
  cancel); emergency stays on R
- 0010: keep procedural rigs, add face/hair/neck; no Mixamo this pass
Linked from architecture.md, PLAN.md, TODO.md, MEMORY.md.
2026-10-31 15:04:12 +01:00
zwitschi 5662baa14d Plan focus cone, head ring, and radial action menu; defer host sign-in
Document the next playable pass: WASD/look stop fighting action keys, cone
focus with a billboarded head ring, doughnut HUD opened by LMB or Space
(click or A/D then Space to confirm, Esc/RMB cancel). Emergency stays on R.
Wand leaves W. NPC rigs stay procedural but get a face/hair/neck. Host
REQUIRE_SIGNIN_VIEW follow-up deferred.
2026-10-31 15:00:11 +01:00
zwitschi b704a8ed61 Move generalized game asset tooling tasks from TODO.md to DONE.md 2026-10-31 14:39:26 +01:00
zwitschi f2d218cc27 Fix publish_release.sh to upload installer + AppImage; document host REQUIRE_SIGNIN_VIEW
Full build / Import + headless tests (push) Successful in 23s
Full build / Full desktop build + optional release (push) Skipped
- Script uploaded only the three portable binaries; v0.2.1 release was
  missing step-line-setup-0.2.1.exe and the AppImage. Now uploads the full
  set (extras when present), reuses existing releases (adds missing assets),
  and falls back to ~/.git-credentials basic auth when GITEA_TOKEN is unset
- Back-filled both missing assets onto the live v0.2.1 release via API
  (release now carries all 5 artifacts)
- TODO/RELEASING/MEMORY document the findings and the host admin steps for
  anonymous downloads ([service] REQUIRE_SIGNIN_VIEW)
2026-10-31 14:37:56 +01:00
zwitschi 3377545a3e Add Python-related entries to .gitignore for improved environment management 2026-10-31 14:33:46 +01:00
zwitschi 7ff0c28c03 Note host sign-in regression for v0.2.1 tag page in the distribution follow-up
Full build / Import + headless tests (push) Successful in 22s
Full build / Full desktop build + optional release (push) Skipped
2026-10-31 14:28:48 +01:00
zwitschi 2ca0e18018 Close out installer/AppImage verification; note v0.2.1 release; document NSIS/appstream gotchas
Full build / Import + headless tests (push) Successful in 24s
Full build / Full desktop build + optional release (push) Skipped
- CI run 1110 proved NSIS + AppImage packaging and published v0.2.1
  (installers uploaded); Mark TODO installer/appimage items done (moved to
  DONE.md), README Latest -> v0.2.1, MEMORY records NSIS -D/absolute-path/
  BMP-ICO gotchas and appstreamcli strictness
2026-10-31 14:28:36 +01:00
402 changed files with 26749 additions and 1257 deletions

No files matched your search

+11 -8
View File
@@ -84,19 +84,19 @@ jobs:
# NOTE: on Linux, makensis uses -D defines (the /D form is Windows-only
# and is rejected as a script path), and it resolves relative paths
# against the script directory — pass absolute paths from the workspace.
cp assets/textures/icon.ico packaging/windows/step-line.ico
cp assets/textures/icon.ico packaging/windows/gig-line.ico
makensis \
-DVERSION="$VERSION" \
-DARTIFACT="$PWD/build/step-line-windows-$VERSION.exe" \
-DICON="$PWD/packaging/windows/step-line.ico" \
-DOUT="$PWD/build/step-line-setup-$VERSION.exe" \
packaging/windows/step-line.nsi
-DARTIFACT="$PWD/build/gig-line-windows-$VERSION.exe" \
-DICON="$PWD/packaging/windows/gig-line.ico" \
-DOUT="$PWD/build/gig-line-setup-$VERSION.exe" \
packaging/windows/gig-line.nsi
- name: Verify Windows installer contents
run: |
VERSION="$(sed -n 's/.*config\/version="\([^"]*\)".*/\1/p' project.godot)"
ls -la build/step-line-setup-$VERSION.exe
7z l build/step-line-setup-$VERSION.exe | grep -E "step-line.exe|uninstall.exe"
ls -la build/gig-line-setup-$VERSION.exe
7z l build/gig-line-setup-$VERSION.exe | grep -E "gig-line.exe|uninstall.exe"
- name: Package Linux AppImage
run: |
@@ -106,13 +106,16 @@ jobs:
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: step-line-build
name: gig-line-build
path: build/*
if-no-files-found: error
- name: Publish Gitea release (tags only)
if: startsWith(github.ref, 'refs/tags/')
env:
# An explicit repo secret wins. Without one, publish_release.sh reuses
# the local git extraheader that actions/checkout configures for the job
# (the runner's own credentials), so tag builds still publish.
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
VERSION: ${{ github.ref_name }}
run: |
+53
View File
@@ -1,3 +1,27 @@
# Python
__pycache__/
*.py[cod]
*.pyo
*.pyd
*.pycache
*.pytest_cache
*.mypy_cache
*.coverage
coverage.xml
htmlcov/
dist/
build/
eggs/
*.egg-info/
.tox/
.nox/
*.venv/
venv/
ENV/
env/
env.bak/
venv.bak/
# Godot 4
.godot/
*.import
@@ -20,3 +44,32 @@ user://
build/
.env
# AI / asset-development outputs (regenerated by the tools; not shipped in the game)
assets/models/concepts/
assets/models/npc/*.blend1
assets/textures/npc/ai/
assets/textures/ai/*
!assets/textures/ai/game_ready/
# Godot extracts embedded glTF textures next to the source on import
assets/models/generated/npc_base_*_albedo.png
assets/models/generated/npc_base_*_normal.png
# Local AppImage extraction
squashfs-root/
# Blender asset library: per-asset Cycles renders are local review output (the
# committed review artefacts are docs/blender_previews/sheet_*.jpg), and Godot
# extracts the GLB-embedded textures next to the models on import.
docs/blender_previews/*.png
assets/models/blender/**/*.png
!assets/models/blender/**/*_ao.png
assets/models/blender_src/*.blend1
# Editable Blender sources are developer-only; keep the .gdignore marker tracked.
assets/models/blender_src/*.blend
# Realism pass (tools/scene_concepts.py): candidate pools, per-run captures and
# generated reports stay local; validated picks are tracked under
# game_ready/v2/picks/ (ADR-0014). Concept references are committed in
# docs/screens/concepts/, but generated QA lives outside the repository.
assets/textures/ai/game_ready/v2/*
!assets/textures/ai/game_ready/v2/picks/
docs/screens/realism/
+77 -16
View File
@@ -14,6 +14,14 @@ The project should be developed in small, reviewable steps. Agents must keep pla
- `MEMORY.md` — Persistent session memory for important decisions, discoveries, unresolved questions, constraints, and useful implementation context.
- `README.md` — Root-level documentation for the project. It should explain what the game is, how to work on it, how to run it, and where to find project information.
- `AGENTS.md` — This collaboration and agent workflow guide.
- `CHANGELOG.md` — The version history of user-facing changes. Keep an entry for each patch, minor, and major version.
- `docs/development-status.md` — The implemented-features list and what is not yet implemented. Update it whenever a feature lands, is renamed, or is removed.
- `docs/architecture.md` — The arc42-style architecture documentation (goals, constraints, building blocks, runtime views, decisions, risks).
- `docs/gameplay.md` — Gameplay systems, venues, and the escalation model.
- `docs/asset-tooling.md` — Asset pipelines, model tools, and screenshot gates.
- `docs/getting-started.md` — Setup, run/test commands, and the controls table.
- `docs/smoke-checklist.md` — The manual smoke-test checklist.
- `docs/adr/` — Architecture Decision Records.
## Required Workflow
@@ -36,9 +44,19 @@ Do not assume that a task is unimplemented without checking the code and documen
All planned implementation work must be represented in `TODO.md`.
- Use headings to group related work into sections.
- Use checkbox items for each concrete task.
- Use level-2 headings (`##`) for milestones or stable themes. A milestone should state its goal
and an explicit exit criterion; a theme groups small related work that does not need a
milestone.
- Use level-3 headings (`###`) for workstreams inside a milestone (for example, `0.8.1
First-shift tutorial`). Keep the hierarchy to three levels: milestone → workstream → checkbox.
- Use checkbox items for each concrete task, normally under a level-3 workstream. Direct
checkboxes under a level-2 section are acceptable for small, single-workstream sections.
- Keep tasks small enough to complete and review independently.
- Add new tasks when discoveries expand the scope.
- When starting a task, claim it for the agent workflow by changing `[ ]` to `[-]`. Keep it `[-]`
while implementation or verification is in progress; only change it to `[x]` after the work and
relevant checks are finished. If work is abandoned or blocked, leave a clear note and return the
item to `[ ]` (or add a follow-up task).
- Mark a task complete only after the implementation and relevant verification are finished.
- If any verification check fails, do not mark the task complete. Fix the failure, or if it cannot be fixed, add a follow-up task to `TODO.md`, record the failure in `MEMORY.md`, and report it in the handoff summary.
- Do not use prose status updates as a substitute for checkboxes.
@@ -49,11 +67,20 @@ Recommended format:
```md
# TODO
## Core Gameplay
## Milestone 0.8.0 — First hour and content depth
- [ ] Add player movement controls.
- [ ] Implement NPC approach behavior.
- [x] Define escalation levels.
### 0.8.1 First-shift tutorial
- [x] Write the tutorial beat sheet.
- [-] Implement the tutorial state machine. (claimed; implementation/verification in progress)
- [ ] Add the tutorial smoke test.
### 0.8.2 Campaign shell
- [ ] Define campaign data.
- [ ] Persist campaign progress.
**Exit:** a first-time player completes a shift without external documentation.
## Documentation
@@ -62,14 +89,26 @@ Recommended format:
### 3. Move Completed Sections to `DONE.md`
When every task in a `TODO.md` section is complete:
`TODO.md` uses nested sections. Apply these completion rules:
1. Verify the work and update the checkbox items to `[x]`.
2. Move the entire completed section, including its heading and checklist, to `DONE.md`.
3. Remove that section from `TODO.md`.
4. Add a short completion note or date when useful.
1. **Level-3 workstream:** verify the work, mark every checkbox `[x]`, and add a short completion
note. Leave the completed workstream under its parent milestone; do not move an isolated
level-3 section to `DONE.md`.
2. **Level-2 milestone/theme:** it is complete only when its direct checkboxes and **all** of its
level-3 workstreams are complete. Verify the whole milestone, record its capabilities in
`docs/development-status.md` when it represents shipped functionality, and write the matching
`CHANGELOG.md` entry.
3. Move the **entire level-2 section**, including all level-3 headings and checklists, to
`DONE.md`. Remove it from `TODO.md` and add a completion date/note.
4. **Planning/overview sections** such as a gap analysis, roadmap overview, dependencies or
deferred ideas are not completion milestones. Do not move them merely because their checkboxes
are empty or complete; update or supersede them as the roadmap changes.
5. Do not move partially completed milestones. Leave them in `TODO.md` until every nested task is
complete, or split the unfinished work into a new smaller level-2 milestone first.
Do not move partially completed sections. Leave them in `TODO.md` until all of their tasks are complete, or split the section into smaller sections first.
When a completed milestone represents shipped functionality, also record the new capability in
`docs/development-status.md` so the implemented-features list stays accurate, and write a matching
entry in `CHANGELOG.md` (see "Maintain `CHANGELOG.md`" below).
### 4. Maintain `MEMORY.md`
@@ -78,7 +117,7 @@ Use `MEMORY.md` for information that should persist between sessions but does no
Record items such as:
- Important design and architecture decisions
- Current implementation state
- Notable implementation state and gotchas (the canonical implemented-features list lives in `docs/development-status.md`, not here)
- Known bugs and workarounds
- Commands needed to run, test, or build the project
- Decisions that future agents should not revisit unnecessarily
@@ -128,9 +167,27 @@ The root-level `README.md` is the project’s public-facing technical and projec
- Links to `PLAN.md`, `TODO.md`, `DONE.md`, `MEMORY.md`, and this file where appropriate
- Known limitations and contribution guidance
Update `README.md` whenever setup instructions, commands, project structure, or user-facing behavior changes.
Update `README.md` whenever setup instructions, commands, project structure, or user-facing behavior changes. The README is intentionally concise and links to the `docs/` files; keep `docs/development-status.md` (and the other docs it links to) synchronized with the implementation rather than pasting detail back into the README.
### 7. Implement in Small Increments
### 7. Maintain `CHANGELOG.md`
The root-level `CHANGELOG.md` records the user-facing changes for every release. It exists so players and contributors can see what changed between versions without reading the commit log.
- Add an entry for every patch, minor, and major version change.
- Follow Semantic Versioning (`major.minor.patch`): bump major for breaking or vision-level changes, minor for new features, and patch for fixes and small polish.
- Group each entry under `Added`, `Changed`, `Fixed`, and `Removed` headings, and date every entry.
- Describe changes in user-facing terms, not as raw commit messages.
- Add the entry when its work ships. If the version number is not yet known, record it under `Unreleased` and move it into a numbered version when the version is tagged.
When a full executable level-2 milestone/theme in `TODO.md` is implemented, completed, and moved to `DONE.md`:
1. Verify the work and mark every checkbox in the section `[x]`.
2. Use the section heading and its completed items to describe the changes and write a `CHANGELOG.md` entry under the next appropriate version.
3. Move the section to `DONE.md`.
Do not move a completed section to `DONE.md` without adding its `CHANGELOG.md` entry.
### 8. Implement in Small Increments
For each task:
@@ -138,9 +195,13 @@ For each task:
2. Inspect the relevant files and dependencies.
3. Make the smallest coherent change that completes the task.
4. Run the relevant tests, checks, or manual verification.
5. Update documentation and `MEMORY.md` if the change creates lasting context.
5. Update documentation if the change creates lasting context: add features to `docs/development-status.md`, adjust the README links or other `docs/` files as needed, and record decisions/gotchas in `MEMORY.md`.
6. Mark the task complete.
7. Move its section to `DONE.md` when the entire section is complete.
7. Keep completed level-3 workstreams in place until the parent milestone is complete; then move the entire level-2 milestone to `DONE.md` after recording its `CHANGELOG.md` entry.
8. Commit the changes to the version control system with a descriptive message.
9. If a level-2 milestone from `TODO.md` has been fully completed, ensure its `CHANGELOG.md` entry is in place, then create a new version or tag in the version control system to mark the milestone.
10. Push the committed changes to the shared repository if applicable.
11. Note the new version or tag created in the version control system in any relevant documentation or handoff summary.
Avoid unrelated refactors unless they are required for the task or explicitly approved.
+393
View File
@@ -0,0 +1,393 @@
# Changelog
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.
## [0.9.0] — 2026-11-01
### Added
- Master/SFX/ambience volume controls, a captions toggle and a Reduce motion
option in Settings.
- A 24-NPC performance budget smoke and expanded audio/accessibility coverage.
- Explicit production-quality scope for animation, audio, localization and the
Godot 4.3-CI/4.7-local support matrix.
### Changed
- Coat and dress strides are shortened to reduce hem/leg clipping.
- Help and the shift-review surface expose the current policy and accessibility
settings.
### Fixed
- Audio cue volumes now respect the player's mixer settings while remaining
headless-safe.
## [0.8.1] — 2026-11-01
### Fixed
- Normal radio reports now send on click. The button was wired only to the
emergency hold-to-confirm handlers, so a non-emergency press did nothing.
## [0.8.0] — 2026-11-01
### Added
- A first-shift tutorial with contextual prompts, a safe visitor and replay/skip.
- A three-venue campaign shell with shift progression and a Continue action.
- Resume-from-checkpoint for paused shifts, including NPC, incident and system state.
- Easy/normal/hard difficulty presets for spawn pacing, compliance tolerance and
report strictness.
- A searchable in-game Help overlay with controls and the current venue policy.
- Recovery events, contextual NPC dialogue and an eligible-event debug preview.
### Changed
- The shift review now includes venue policy, campaign next steps and continued
review controls.
- Event scheduling suppresses recently played scripted events and respects the
difficulty spawn multiplier.
- Save data is written atomically and split into settings, campaign, shift
history and versioned checkpoint files.
### Fixed
- Missing/older settings migrate through defaults; a partial write cannot
replace a valid save.
- The HUD debug overlay now refreshes and shows the next eligible events.
## [0.7.3] — 2026-11-01
### Fixed
- Mouse look now reads relative motion at the `_input` stage, so a full-screen
HUD or focused control cannot swallow it while the cursor is captured.
- Mouse capture is restored after window/compositor focus returns, and clicking
back into a gameplay window recaptures it instead of opening the radial menu.
- The HUD is mouse-transparent; its presentation controls no longer block
gameplay mouse input.
## [0.7.2] — 2026-11-01
### Added
- Automatic OpenRouter model selection for the screenshot judges: the cheapest
currently available image+JSON-capable model is used, with ordered fallbacks
and a pinned `GIG_LINE_JUDGE_MODEL`/`--model` override.
### Changed
- The judge reports the model that produced each verdict and fails clearly on an
invalid API key instead of retrying other models.
- The screen-design and realism reports now include the fresh judge results.
### Fixed
- The blocked screenshot judge now runs with the valid `OPENROUTER_API_KEY`
(`13/13` screen designs match, `0` diff).
## [0.7.1] — 2026-11-01
### Added
- An optional Desktop HQ build for Windows and Linux that uses Forward+,
volumetric fog and SDFGI, packaged with an `override.cfg` beside the
executable.
- Volumetric flashlight haze in the Desktop HQ build, with a LATE-phase preset
tuned so the entrance facade stays readable.
### Changed
- The normal Compatibility build remains the default; the preset only affects
the extracted Desktop HQ copy.
- Night-time light falloff adapts to Godot 4.7 while keeping the 4.3 baseline
unchanged.
### Fixed
- The flashlight no longer disappears at night on Godot 4.7 under the old
attenuation curve.
- Headless and Web builds stay non-volumetric and boot normally when Forward+
is unavailable.
## [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
- Per-phase AI skyline panoramas (day, dusk, night) replacing the flat box skyline.
- Higher-detail surface textures with normal maps (brick, concrete, asphalt, awning, and more).
- A runtime image root (`assets/images/`) with a UI and scene art loader; the start menu,
pause, settings, shift review, radio, wand, inspection, and radial menu are styled against
their mockups.
- NPC face and hand detail: eyes, nose, lips, ears, fingered hands, and cuffed sleeves.
- A staged NPC portrait capture for judging avatar quality up close.
- Gear-fit checks (`tools/blender_assets.py fit`) and regression tests for head and back gear.
### Changed
- Scene lighting, fog, sun angle, and night glow retuned to match the AI concept frames;
the Late phase uses wet asphalt.
- The flashlight is a tighter forward spot that reads in the dark.
- Sunglasses narrowed to sit inside the face silhouette; shoulder bag moved clear of the
coat and dress skirts; firefighter helmet lifted off the brow.
- The README now links to focused docs pages for gameplay, architecture, setup, asset
tooling, development status, and the manual smoke checklist; `PLAN.md` was compacted
into a design plan.
### Fixed
- Sidewalk overexposure at the entrance reduced to match the concept target.
- The body-camera overlay no longer overlaps the shift/reputation/crowd status.
- Selecting a venue releases the menu input lock and recaptures the mouse.
- The shoulder bag no longer clips through the coat's skirt; the firefighter helmet no
longer cuts into the brow ridge; the portrait subject now faces the camera.
- `--portrait-type` is now parsed and applied instead of being ignored.
## [0.6.0] — 2026-11-01
### Added
- Blender-authored character, gear and prop library with baked ambient occlusion.
- Higher-resolution environment textures with tangent normal maps.
- Phase-driven emissive details on library props, characters and street dressing.
### Changed
- The game was renamed from "Step Line" to "Gig Line".
### Fixed
- NPCs now face their movement direction instead of walking backwards.
## [0.5.0] — 2026-11-01
### Added
- Articulated NPC characters with layered outfits, hand items, face/hair variety,
idle behaviour, reaction symbols, queue offsets and three LOD tiers.
- Player acceleration/braking, step-up assist, head bob, landing dip,
precision-walk feedback and surface-aware footsteps.
- Remappable keyboard and gamepad controls.
### Changed
- Crowd roles now select deterministic character variants and props.
## [0.4.0] — 2026-11-01
### Added
- Collision and navigation footprints for every street prop.
- Zoned prop placements with deterministic variants and small clutter.
## [0.3.3] — 2026-11-01
### Added
- Staged in-game captures for the wand screening, escalation HUD, and body-camera timeline screens.
## [0.3.2] — 2026-11-01
### Added
- AI reference mockups for the seven remaining catalogue screens.
## [0.3.1] — 2026-10-31
### Added
- Prop models drawn with a single texture atlas (one material per prop).
### Changed
- The Web build is listed in the README downloads table.
## [0.3.0] — 2026-10-31
### Added
- Surface-material profiles, ACES tonemapping, glow, aerial fog and phase-based
colour grading.
- Framed facade windows, door trim, curb and lane markings.
- Generated street-furniture models and the Web export.
- Aim-free targeting, a focus cone and precision walk.
## [0.2.16] — 2026-10-31
### Added
- Venues authorable as editor `.tres` resources.
## [0.2.15] — 2026-10-31
### Added
- A vision-model judge for comparing screenshots against reference designs.
## [0.2.14] — 2026-10-31
### Added
- Anonymous release downloads enabled on the host.
### Fixed
- `Strings.fmt` keeps positional placeholders stable when new keys are added.
## [0.2.13] — 2026-10-31
### Added
- Missing scenario content: Joke Shop reservation/ID cases, wand refusal and late
arrival events, the studio filming-boundary repeat, and a celebrity-arrival crowd spike.
## [0.2.12] — 2026-10-31
### Fixed
- Release uploads fail loudly instead of leaving an empty release with a green job.
- Pruned releases restored and CI publishing verified end to end.
## [0.2.11] — 2026-10-31
### Fixed
- Releases publish using the runner's checkout credentials when no token or git
credentials exist.
## [0.2.10] — 2026-10-31
### Fixed
- Release publishing fixed with a CI token fallback.
## [0.2.9] — 2026-10-31
### Added
- Accessibility pass: escalation feedback that reads in grayscale and a working HUD font scale.
## [0.2.8] — 2026-10-31
### Added
- Procedural crowd ambience that rises with crowd mood, and a radio crackle.
## [0.2.7] — 2026-10-31
### Added
- NPC theater poses: sitting, taking photos, filming, and performing.
## [0.2.6] — 2026-10-31
### Added
- A body-camera timeline on the shift review for stepping through recorded events.
## [0.2.5] — 2026-10-31
### Added
- Emergency aftermath: evacuation clears the entrance, and unjustified calls write
an investigation note with a false-report reputation hit.
## [0.2.4] — 2026-10-31
### Added
- Crowd presentation: onlookers rubberneck at incidents and clear out during panic.
### Fixed
- NPC navigation issues resolved.
## [0.2.3] — 2026-10-31
### Added
- Remaining PLAN actions: eye contact, "I recognize this person", a formal-warning
body-camera flag, blocking that stations the guard in front of the door, and
requesting a reason for entry.
### Fixed
- A dead incident-recording path.
## [0.2.2] — 2026-10-31
### Added
- Session shell: startup menu with venue selection, pause overlay, settings
persistence, and a "run another shift" flow back to the menu.
- Control pass: focus cone, head ring, radial action menu, and named input actions.
### Fixed
- Release publishing now uploads the installer and AppImage.
## [0.2.1] — 2026-10-31
### Added
- Windows NSIS installer and Linux AppImage packaging; all exports embed the PCK.
- An AI texture generation mode in the asset pipeline.
### Fixed
- NSIS build fixed for Linux (absolute defines, space-free installed exe, BMP icon).
- Runtime hygiene: RefCounted cycle cleanup, full-length shifts, and a wand cooldown.
## [0.2.0] — 2026-10-31
### Added
- First playable build of the security-guard simulation at the Hollywood Boulevard entrance.
- Core simulation: shift coordination, venue scenarios, event scheduling, NPC movement
and states, and escalation from observation through deterrence, warnings, and blocking.
- Access verification for wristbands, IDs, tickets, credentials, and guest lists, with
admit/redirect/hold/refuse decisions and feedback scoring.
- Escalation levels 0–5 with radio assistance and a hold-to-confirm emergency call.
- Incident recording, body-camera evidence, reputation scoring, crowd reactions, and
an end-of-shift review.
- Scenario content for Hollywood Boulevard, The Joke Shop, and the TV Studio, including
a mental-health crisis event.
- A protester NPC, the "call someone else" reaction, a lockable door, and a flashlight.
- Save/persistence, navmesh pathfinding, and JSON localization.
- Visual polish: phase lighting, street lamps, marquee, street dressing, and NPC variety.
- Detailed graphics: authored textures, a humanoid NPC rig, and HUD/inspection feedback.
- Downloadable Linux, Windows, and macOS builds published by CI.
### Removed
- The dialogue tone-of-voice picker (reactions now depend on escalation level and NPC profile).
+1311 -8
View File
File diff suppressed because it is too large. Load diff
+584 -13
View File
@@ -4,28 +4,556 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
## Decisions
- The visual-alignment path/loader/panel/HUD work and the realism lighting pass are complete and
recorded in `DONE.md`; remaining visual items are the realism follow-ups in `TODO.md`.
- `PLAN.md` is a compact product/design document, not an implementation log. Historical control
and v0.3.0 specs were folded into its Presentation and Controls section; put new step-by-step
work in `TODO.md`, not the plan.
- Editable Blender sources under `assets/models/blender_src/` are gitignored and untracked
(46 `.blend` files removed from the index on 2026-11-01); the `.gdignore` marker stays tracked.
- `screen_designs.py judge` default `google/gemini-2.5-flash-lite` worked on 2026-11-01
(7 match / 4 review / 0 diff); `--model` remains available.
- `capture_realism.gd` parses `--portrait-type=N`; it was documented but ignored before the gear
pass. Portrait captures need the requested NPC type to have spawned, otherwise the stage can
finish with an empty frame; use the Blender character previews (`render --only npc_coat`) for
deterministic gear checks.
- Headgear: the firefighter helmet was lifted 2 cm after the brow/face pass; cap and hood were
visually checked on `npc_vest`/`npc_hoodie`. The beanie is unused and shares the cap shell.
- Gear fit is measured with `python3 tools/blender_assets.py fit` (stdlib, reads the GLBs):
head width uses skin vertices weighted to Head in y 1.55–1.81; clothing radius is the cloth
skirt in y 0.30–0.52 with a pants hip fallback. The command reports per-character gear boxes,
head-band status and x/radial skirt clearance; `test_blender_assets` covers its shape.
- Post-realism work is split by theme, not by date: finished items are archived in `DONE.md`, and
`TODO.md` carries the v1.0.0 gap register and milestone roadmap. New work belongs there, not in
a revived historical plan document.
- Graphics v0.6.0 plan (completed 2026-11-01; ADR-0013): NPCs move to Blender-authored skinned meshes. `tools/npc_pipeline.py` builds
`assets/models/generated/npc_base.glb`; `NpcAvatar` keeps the procedural `_build_body()`
fallback. Preserve `Rig/Head`, the limb node names, and the seed-deterministic
`outfit_for`/`layer_for`/`hand_item_for` helpers. Blender = flatpak 5.2.0 LTS, sandbox cwd
`/app/blender` → absolute paths for `--python`. CI stays offline (committed assets).
- Engine: Godot 4.3 stable (official editor build, `~/.local/bin/godot`). The system's preinstalled `godot-runner` (4.7.2, Fedora) cannot load projects from arbitrary paths because it was compiled without path overrides; use the official editor binary instead.
- Language: GDScript. All cross-file references use explicit `preload()` constants (not `class_name` globals) so scripts compile in headless `--script` runs without the editor's class cache.
- Test framework deviation: the project uses a custom minimal headless test runner (`src/test/test_runner.gd`) instead of GUT, because GUT needs the editor plugin. CI runs it with `--headless`.
- Eventing: no autoload signal bus. `ShiftCoordinator` owns an `EventBus` instance (`src/core/event_bus.gd`) passed to subsystems; subsystems emit only when a bus is assigned. Autoloads at parse time are unreliable under `--script`.
- Static signals are not available in Godot 4.3 GDScript ("Expected func or var after static") — do not use them.
- GDScript lambdas capture local variables **by value**; mutating a captured local inside a lambda does not affect the outer variable. Use member functions for accumulators in tests.
- Prototype shift duration defaults to 300 s for testing; production targets 10-15 minutes per PLAN.md.
- Shift length is the scenario's `shift_duration_s` (900 s in all three venues). Tests and the screenshot tool override elapsed time; they do not change the default.
- RNG: NPC reactions and event scheduling are seedable for deterministic tests.
- **Mouse look (v0.7.3):** guard look is handled in `GuardController._input()` (before GUI), not `_unhandled_input`; focus/click-back recaptures the cursor and the HUD is made mouse-transparent recursively. Regression tests cover yaw/pitch and HUD filters.
- **v0.9.0 production pass:** CI/release baseline remains Godot 4.3; local dev is 4.7.2 with the version-adaptive light falloff. Audio settings are `master_volume`/`sfx_volume`/`ambience_volume`; accessibility has `reduce_motion` and `captions_enabled` (HUD log is the caption surface). Budget smoke is 24 NPCs (6.94 ms headless). Coat/dress stride is scaled to 0.76 to reduce hem clipping. Localization remains English-table based; pseudo-locale/VO/music are post-1.0.
- **Radio submit fix (v0.8.1):** `RadioPanel.action_button` now connects `pressed` for normal reports; `button_down/up` remains exclusive to emergency hold-to-confirm. Regression test covers both modes.
- **Campaign/v0.8.0:** `Campaign` owns Hollywood → Joke Shop → TV Studio progression. `campaign.json`, `shift_results.json`, `settings.json` and versioned `shift_state.json` persist separately; pause saves a checkpoint, review Continue advances, Settings resets with confirmation. Difficulty keys are `easy/normal/hard` (`ai_difficulty`); scripted QA bypasses tutorial unless `--tutorial` is passed. Help is searchable and generated from `InputBindings.REMAPPABLE`.
- **Tutorial (0.8.0 work):** `docs/tutorial.md` is the eight-beat design; `src/game/tutorial.gd` is the pure state machine, `TutorialOverlay` is the non-blocking card, and `GameManager` wires it with a safe visitor, suppressed spawns, persisted completion/skip and Settings replay. Scripted test/capture runs bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers completion/replay/skip.
- **Documentation ownership (2026-11-01):** `docs/architecture.md` §12 defines the source-of-truth map and consolidation rules. Obsolete/historical plan documents are removed rather than kept; current work belongs in `TODO.md`. README/development-status/Blender AO/Forward+ stale claims were corrected. The v1.0.0 gap register and 0.8/0.9/0.9.5/1.0 roadmap live in `TODO.md`.
- **Vision judge (v0.7.2):** `tools/openrouter_judge.py` reads `/api/v1/models`, filters image-input models with `response_format`, and ranks by advertised prompt/image/completion prices. `--model auto` (default) uses the cheapest (`z-ai/glm-5.3-flash` on 2026-11-01) with fallbacks; explicit `--model`/`GIG_LINE_JUDGE_MODEL` wins. Auth errors abort, provider/model errors fall through. Full screen judge 13/13 match, 0 diff; A/B judge ~$0.006.
- **Desktop HQ preset (v0.7.1):** `packaging/desktop_hq/override.cfg` switches a
copied executable to Forward+; `Materials.forward_plus_active()` gates
volumetric fog/SDFGI and `light_volumetric_fog_energy`, and
`Materials.modern_light_falloff()` compensates for Godot 4.7's much harsher
local-light falloff. LATE volumetric density is 0.020 with flashlight
volumetric energy 3.0. `tools/build_releases.sh` packages the Linux/Windows
binaries + override into `gig-line-desktop-hq-<version>.zip`;
`publish_release.sh` uploads it when present. A/B report:
`docs/screens/realism/2026-11-01-forward-plus/` (entrance gap 1.568→1.187).
- **Local Godot toolchain (2026-11-01):** `~/.local/bin/godot` is 4.7.2 stable;
4.3 stable is kept as `~/.local/bin/godot-4.3.stable`. Export templates for
4.7.2 are installed under `~/.local/share/godot/export_templates/4.7.2.stable`
(4.3 remains). The project and CI still pin 4.3 in `project.godot`/the Gitea
workflow; 4.7.2 imports, runs the 231-test suite, session-shell smoke and a
Linux export successfully. Godot 4.7 writes `*.gd.uid` files; they are kept
out of this clone's status via `.git/info/exclude` until the project upgrades.
- **Blender invocation (v0.6.0):** `flatpak run org.blender.Blender --background --python
/run/media/system/DATA/Documents/06-games/security-game/tools/npc_pipeline.py -- build`.
The flatpak cannot read `/tmp`; use absolute repo paths. `assets/models/npc/.gdignore` is
required, otherwise Godot's editor tries to import the committed `.blend` and hangs during
`--import` (also add `.gdignore` to local build/squashfs folders when scanning is slow).
- **Skinned NPC integration (v0.6.0):** `NpcAvatar._build_skinned_body()` loads
`npc_base.glb` and keeps `_build_body()` as fallback (`use_skinned_model=false` in tests).
Bone pose rotation is the **absolute local orientation**, not an offset from rest: compose with
`get_bone_rest(i).basis` or the whole body snaps to a broken pose. Proxies named Hip/Knee/
Shoulder/Elbow/Hand + Head marker preserve the old animation math; `Rig/Head` is the marker.
Five material slots are tinted via `SurfaceMaterials.npc_slot()`; keep `_rig.scale` uniform on
the skinned path. Budget: 16 avatars = **144 meshes**, ~6.9 ms headless.
- **NPC facing (fixed after v0.5.0):** the avatar's visual front is **+Z** (procedural eyes
and the Blender model's face). Godot `look_at()` aims **-Z**, which made every NPC walk
backwards. Use `NpcAvatar.yaw_towards(dir)` and set `rotation.y`; do not call `look_at()` on
avatars. Head-turn offsets use `+basis.z` for the same reason. Covered by
`test_yaw_faces_the_movement_direction`.
- **Image root (ADR-0014, 2026-11-01):** `assets/images/` is the runtime root for composed
interface/scene art (`ui/`, `scenes/`); tiled PBR textures stay under `assets/textures/`;
`docs/screens/designs/` and `docs/screens/concepts/` stay reference-only; QA captures stay in
`docs/screenshots/`. When a mockup becomes runtime art, copy/process it into `assets/images/`
with a new name (no overwrites) and keep a fallback in code.
**Mockup copies:** 21 design references live in `assets/images/ui/`; `docs/screens/designs/`
remains the reference-only copy. `tools/screen_designs.py` reads/writes the runtime root.
## Handoff (read this first)
- **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,
`blender_assets.py audit` exit 0, `smoke_npc_budget` OK (16 avatars, 6.94 ms of 50 ms).
`PLAN.md` is compact; README/architecture track the current controls, asset pipeline, CI and
release artifacts. The historical notes below remain useful for implementation gotchas; for
current tasks use `TODO.md`.
- **Phase 2 — AI texturing (this agent, DONE 2026-11-01).** Phase 1 assets were
already in the tree (untracked) and are preserved. Generated six seamless AI
clothing albedos into `assets/textures/npc/ai/{shirt,trousers,jacket,vest,
coat,dress}.png` (512×512, `--role tile`, gpt-5-image-mini; audit 6 good).
**Gotcha:** `asset_pipeline.py generate` defaults to `GEN_SIZE=2048`, which
gpt-5-image-mini rejects (HTTP 400); pass `--request-size 1024` for 1:1 roles.
`generate --role tile` already applies `make_seamless`, so a separate
`seamless` pass is redundant. Added to `tools/npc_pipeline.py`: `prepare`
(layers AI shirt→cloth, trousers→pants albedos at 128px) and `palette`
(writes `assets/textures/npc/palette.json` via `model_pipeline.dominant_colors`).
`write_slot_textures()` now skips the albedo for slots with an AI override
(normal/ORM stay procedural). Blender `build` re-run: GLB audit ok, 1204 tris /
2488 verts, AI albedo embedded for cloth+pants. Phase 2 completed and shipped with
the v0.6.0 asset library.
**Import concurrency:** a parallel agent is running
`timeout 300 godot --headless --editor --quit --path .` and then checks
`assets/models/generated/npc_base.glb.import` — treat that as the canonical
import. My own `godot --headless --path . --import` hung with no output past
the engine banner (two Godot instances contended on `.godot`); I killed the
orphan (PID 2482934). Do not run a second import while that `--editor --quit`
is active.
Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes
`palette.json` and the GLB; Phase 2's capture/judge item is blocked on that.
- **Git hygiene + tooling (2026-11-01):** dev asset folders are now gitignored
and untracked: `assets/models/concepts/`, `assets/models/npc/` (Blender
sources), `assets/textures/npc/ai/` (raw AI fabrics), and the raw
`assets/textures/ai/*` outputs (only `assets/textures/ai/game_ready/` stays).
Kept in the repo: `assets/textures/npc/*_{albedo,normal,orm}.png` +
`palette.json`, `assets/models/generated/*` (props + npc_base.glb), and the
authored `assets/textures/*` set. `tools/asset_pipeline.py generate` gained
`--variants N` — generates N alternatives saved as `<stem>-1..N.png` (N=1
keeps the unsuffixed path). Python tooling tests still pass (exit 0).
- **Screenshots + docs (2026-11-01):** regenerated the eleven README captures
(`capture_screenshots.gd` + `capture_prop_gallery.gd`) — ten changed, the
prop gallery is byte-identical (props unchanged). README gained an NPC
character-model subsection, a "Confirmed game assets" section (linked
`game_ready` textures, generated models, NPC maps), and refreshed
dev-status / not-yet lines; `docs/architecture.md` Level 2 now lists
`npc_pipeline.py` + `asset_pipeline.py` and the confirmed-asset locations.
- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at
`/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.3.stable).
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**.
Python tooling tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs test_model_pipeline` → **53**.
Session shell smoke: `godot --headless --path . --script res://src/test/smoke_session_shell.gd` → exit 0.
Windowed control smoke: `godot --path . --script res://src/test/smoke_focus_radial.gd` → exit 0
(now also covers wheel cycling and right-click clearing).
- `main` is in sync with origin. **v0.2.2** tags the session-shell + world-follow-up commit;
CI builds the portable binaries, NSIS installer, and AppImage on the tag push.
- **v0.3.0 (2026-10-31, working tree):** rendering pass (`src/game/materials.gd`, phase-driven
environment), generated `.glb` props (`tools/model_pipeline.py`, `assets/models/generated/`),
aim-free selection with wheel cycling and Shift precision walk (ADR-0011/0012), and a **Web**
export target. `export_presets.cfg` gains a `Web` preset with `variant/thread_support=false`
(static hosting, no COOP/COEP); `tools/build_releases.sh` exports `build/web/` and zips it to
`build/gig-line-web-<ver>.zip`, and `publish_release.sh` uploads it. Covered by
`src/test/tests/test_web_export.gd`.
- **Prop atlas (v0.3.1):** `model_pipeline.atlas_for()` builds a per-prop power-of-two PNG with
one 8×8 cell per palette role, plus a metallic/roughness atlas (G=roughness, B=metallic). The
GLB merges all parts into one primitive with constant per-vertex UVs (cell centre) and a single
material (`baseColorTexture` + `metallicRoughnessTexture`, NEAREST sampling). Colours are sRGB in
the texture, so the old `srgb_to_linear`/`_linear_rgba` helpers were removed. `audit_glb` fails
if a prop is not exactly one material or misses TEXCOORD_0/images.
- **Mockups complete (v0.3.2):** `docs/screens/designs/` holds AI references for all 19
catalogue screens. Only the six staged captures (01–06) are compared/judged; the other 13
have no `capture`, so adding staged captures for the wand panel, timeline and escalation HUD
would extend automated visual checks. `generate` now calls `_load_env_file()`.
- **Staged captures (v0.3.3):** `capture_screenshots.gd` now emits nine shots. States 7–10 switch
to The Joke Shop at elapsed 620 s (EVENING has routine spawns; LATE has none), retry the wand
until it alerts, then jump to 760 s after staging for night lighting. The timeline needs
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents. The escalation
HUD mockup is third-person now; `judge` still classes it `review` (no diff).
- **Prop collision (v0.4.0):** `_add_prop_model()` fits a `StaticBody3D` from the model bounds
(cylinder for bollard/bin/hydrant, box otherwise) and adds a `NavigationObstacle3D`; NPC agents
enable avoidance and move with the safe velocity from `velocity_computed`. The navmesh stays a
flat plane, so avoidance (not a baked mesh) routes guests around props. Placements live in
`PROP_PLACEMENTS` with `placement_problems()` validation; variants come from `variant_for(index)`.
`smoke_props.gd` must be windowed because the guard reads captured-mouse input.
- **Realism pass (2026-11-01, completed + adopted):** `tools/scene_concepts.py`
(`list`/`generate`/`process`/`promote`/`audit`/`realism`) never overwrites an image
(`next_free_path` adds `-vN`; `process` skips existing unless `--new`; `promote` keeps an
existing pick). Raw AI output: `assets/textures/ai/v2/raw/` (gitignored). Candidate pool:
`assets/textures/ai/game_ready/v2/` (gitignored) and the **tracked runtime assets** are
`.../v2/picks/{skyline_day,skyline_dusk,skyline_night,brick_facade,sidewalk_pavers,asphalt,
asphalt_wet,awning_canvas}.png` (+ `_normal`). Concepts `docs/screens/concepts/` (committed,
`.gdignore`, targets in its README). Runs `docs/screens/realism/<run>/` (PNG captures
gitignored; `report.{json,md}` + `sheet.jpg` committed).
`src/test/capture_realism.gd` runs five stages (day 0 s / evening 620 s / late 760 s /
joke_shop 700 s / studio 700 s; `--stages=` subset) and swaps textures by the material's
*original* albedo path. **Gotcha:** in the SceneTree script, do not overwrite `_phase` after
`_begin_stage()` sets `done`, or the run loops forever between stage/shoot (`_queue` empty).
**Gotcha:** `elapsed_s = 620` is *Evening* (dusk), 700+ is Late. Godot 4.3
`StandardMaterial3D` has no `fog_enabled`; the skyline is a `ShaderMaterial` with
`render_mode unshaded, cull_disabled, fog_disabled` + `ALPHA_SCISSOR_THRESHOLD` so the
panorama's own haze shows. Backdrop plane is 90 m tall with 2 UV repeats (~200×90 m per
repeat, near the 2:1 panorama aspect). Legacy `assets/textures/skyline.png` is no longer
loaded but stays on disk for the `test_world` asset check.
**Phase look:** day sun 0.62 / exposure 0.6 / ambient 0.22; evening sun 0.12 / exposure
0.72 / dark violet sky; late exposure 0.95 / ambient 0.10 with the wet asphalt street and a
bright skyline tint (1.5) so the night windows read. Stripes: `stripe_period()` (detrended
autocorrelation) + `align_stripes()` (resample to whole periods) replace the edge crossfade
for `awning_canvas`; `audit_striped_tile()` compares the wrap step with the harshest interior
step.
**Judge:** the default `google/gemini-2.5-flash-lite` mostly works but intermittently answers
HTTP 400 "API key not valid" (one shot of ten on 2026-11-01); re-run or pass
`--model google/gemini-2.5-flash` when it does. Scores are noisy (±1–2), so
`realism --repeats 3` averages and `summarize()` reports a like-for-like mean over the shots
every set captured. Final: overall 0.8→1.0, backdrop 0.6→1.6, metric gap 0.444→0.348.
`screen_designs.py judge` gained one retry for the same transient 400.
**Character portrait capture (2026-11-01):** `capture_realism.gd` has a `portrait` stage
(`npc_portrait` -> concept `npc_portrait_day`) because crowd shots cannot judge avatars. It
freezes one NPC, parks the rest of the crowd 140 m away (a passer-by otherwise blocks the
frame), moves the subject to an open sidewalk patch, turns it into the camera and stands the
guard on the sunlit side. Needed because the NPC FSM re-writes `rotation.y` every physics tick
(so the yaw must be set after `set_physics_process(false)`), the venue's morning sun leaves a
face in shadow from most angles, and `visible = false` does not keep the crowd out of frame.
`--portrait-type=N` forces a role; hooded roles (trespasser/violent/crisis) are skipped.
**Flashlight (2026-11-01, final):** a *tight* `SpotLight3D` on `Head`: `spot_range` 20,
`spot_angle` 14, `light_energy` 4.2, `spot_attenuation` 0.9, forward `rotation_degrees = 0`,
starts invisible. A fake additive light shaft was tried twice (cylinder with a UV ramp, then a
vertical-fade `ShaderMaterial`) and **rejected both times**: on GL Compatibility a first-person
camera inside the cone renders it as a filled disc and the far ring shows, so
`Materials.light_beam()` and the beam mesh are gone. Regression guard:
`test_flashlight_aims_a_tight_forward_spot` in `src/test/tests/test_door_and_flashlight.gd`
(lamp on the head, aims straight, cone ≤ 20°, starts off). Night captures are staged in LATE
(`elapsed_s` 760), not dusk.
**NPC detail pass (2026-11-01):** `_face()` now builds sclera + iris + upper lid per eye, brow
ridge, cheekbones, nose bridge/tip, ears with lobes and split upper/lower lips; the new
`eye_white` material is a *fixed-colour* slot, so `_apply_outfit_materials()` leaves it white.
`_hands_and_shoes()` has four fingers plus a thumb and `_clothing()` adds `Cuff` tori.
Budget work: `CHAR_MAX_VERTS` 5000 (see the library note), body decimation 0.26, upper lids are
boxes rather than spheres, and knuckle spheres/nostrils were cut to stay inside it.
**Orientation debugging:** to check a GLB's front axis, compare the mean Z of the `eye_white`
surface (face) and of the `rubber` sole verts (toes) — all 8 avatars are +Z, so any
"they walk backwards" symptom is a yaw bug, not an export flip.
- **Blender asset library (v0.6.0, integrated 2026-11-01):**
`tools/blender_assets.py` (`build`/`render` in Blender; `audit`/`sheet`/`list` stdlib) writes
`assets/models/blender/{npc,npc_gear,props}/*.glb`, `manifest.json` (integration source of
truth), sources in `assets/models/blender_src/` (`.gdignore`d), and review sheets
`docs/blender_previews/sheet_*.jpg` (per-asset PNG renders are gitignored). 8 characters, 18
gear, 20 props; notes in `docs/blender-assets.md`. Characters are drop-in for `npc_base`
(same mesh node name, bones at the same rest positions, five tint slots) and stay under the
6000/4000 budget only because of body decimation (0.26) + `smart_project` at 80°: glTF vertex
count includes UV-seam splits, so extra beveled detail quickly breaks the limit. The 2026-11-01
face/hand pass raised `CHAR_MAX_VERTS` to 5000 (tris 3,974–5,026) after measuring 16 avatars at
~6.9 ms of a 50 ms frame; `npc_pipeline.MAX_VERTICES`/`MAX_TRIANGLES` (base mesh) are unchanged.
**Gotchas:** `bisect_plane` and thin bevels leave zero-area triangles (Godot warns "non-finite
normal in LOD generation"); `_clean()` + a weld after bisect fix it, and the audit rejects them.
Fixed-colour detail materials carry a 4×4 texture because `_apply_outfit_materials()` sets
`albedo_color = WHITE` on unknown slots. Cycles CPU render ≈ 3 s/asset; Godot import of the
library is clean.
**Integration:** `NpcAvatar.character_model_for()` selects a variant by role/seed; only the
five contract slots are tinted (detail materials untouched; `npc_vest` keeps its fixed hivis
vest, firefighter/guard get dark uniform tints). Gear is attached at head/back/chest/hand and
falls back to primitives; `npc_hoodie` skips the extra hood gear. `WorldBuilder` prefers
`assets/models/blender/props/` for the six replacements and the new street dressing, fits the
same colliders/obstacles, and collects library emissives for `set_night_mode()`.
`LIBRARY_TRIANGLE_BUDGET` = 60k (current ~27,986). Library props are excluded from
`PROP_PLACEMENTS` only when they need dedicated slots (door/marquee/sedan/handrail/lantern).
**Gear fit (v0.6.1):** `fit` measures head width (0.315 m), skirt/hip radius and gear bounds;
sunglasses are 0.202 m, the bag's body inner face is 0.262 m (coat 0.225, dress 0.260), and the
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, 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
skip, but the day roster spawns none of types 5-7, so a hooded close-up is deferred.
- **v0.6.0 environment pass:** `tools/bake_ao.py` bakes 256² AO maps per library asset
(`<name>_ao.png`, red channel) and `Materials.apply_ao()`/`SurfaceMaterials` wire them in;
`tools/make_normal_maps.py` derives tangent normals for the 1024² brick/concrete/asphalt
textures. Forward+ boots on desktop but is rejected for v0.6.0 (project-wide renderer, Web
must stay GL Compatibility). Headless 222/222, Python 69/69.
- **Prop pipeline (done):** audit enforces 400 tris / 600 verts, ground contact, UV range, no
degenerate tris, one material. `capture_prop_gallery.gd` writes `10-prop-gallery.png` (3x2 studio
line-up, back row on a platform) and the `prop_gallery` catalogue entry compares it.
- **NPC character pass (v0.5.0):** Rig is articulated (`Rig/Hip* -> Knee`, `Shoulder -> Elbow ->
Hand`); `outfit_for` adds layer/hair_style/hand_item; `_idle_layer` drives phone/clipboard/crossed
and escalation gestures; `_reaction_tag` shows `!!`, `?!`, `!`, `?`; `apply_lod` swaps to a
billboard impostor at 26 m (hysteresis 1.5 m); GameManager `_refresh_queue` assigns queue offsets.
Perf: 16 avatars = 534 meshes, ~6.9 ms headless.
- **Player movement (v0.5.0):** **W was inverted** (`get_axis(forward, back)` is negative for W and
the code multiplied it directly); now `forward * -input_dir.y`. Step-up uses test_move + a raised
copy (0.32 m). Guard emits footstep/landed/precision_changed; GameManager forwards them to
AudioFeedback and the HUD. Gamepad actions are added at runtime by
`GuardController._ensure_gamepad_bindings`; remaps persist via `Save` `input_bindings` and are
applied by `InputBindings.apply` (key/button events only). Windowed `smoke_movement.gd` guards it.
- **Scheduled TODO pass complete:** only the optional Future ideas remain (live replay, extra
venues, voice audio, image-to-mesh). Grounding facts: the
NPC rig is rigid capsules with a whole-limb swing, and `guard_controller.gd` uses one `accel`
value. Each task should get its own windowed smoke or capture where it changes feel/looks.
- **Backlog complete:** every planned TODO section is done and moved to `DONE.md`; `TODO.md`
now only lists optional future ideas (live 3D replay, NPC LOD, more venues, real audio, prop
atlas, AI mockups for the new catalogue entries, image-to-mesh props). GUT was consciously not
adopted (custom runner covers it without an editor plugin).
- Do not re-open `override_access`. Do not bind wand to `W`. Do not remove the radio easter egg.
## UI / Presentation Decisions
- Runtime UI/scene art is loaded through `src/game/ui/art.gd` (`assets/images/ui/` then `scenes/`); missing files return null so panels keep procedural fallbacks.
- Screen mockups with baked-in controls are used as backdrops plus an opaque `ArtPanel` cover, not as clickable full-screen skins.
- Radio, wand, inspection, and body-camera timeline panels use their reference art as backdrops; forms sit on transparent, padded panels over the covered mockup controls.
- HUD target names use a boxed chip under the crosshair; escalation has a five-segment bar below the text label. The body-camera REC/clock block starts at y=92 on the right so it clears the shift/rep/crowd text.
- Staged captures in `docs/screenshots/` are committed and were last regenerated after the realism and NPC passes; re-run `tools/check_screenshots.sh` (not a partial capture) before replacing them.
- `tools/check_screenshots.sh` is the repeatable capture+compare smoke; both capture scripts accept `--out=res://docs/screenshots`. Judge verdicts on the staged set were 7 match / 4 review / 0 diff.
- Venue selection used to leave `guard.input_locked` true and the mouse visible; `_start_shift()` must call `_refresh_freeze()` after starting the clock.
## Current State
- **Release v0.2.0 is LIVE**: pushing the `v0.2.0` tag made Gitea Actions run the build job
- **v0.3.0 pass (2026-10-31, historical; shipped):** rendering, generated prop models, screen
catalogue, aim-free selection. See ADR-0011 (selection) and ADR-0012 (materials/props).
- `src/game/materials.gd` (`SurfaceMaterials`) is now the single source of surface
recipes + `configure_environment()`. `GameManager.PHASE_LIGHTING` gained
`exposure/glow/fog_color/fog_density/fog_energy/contrast/saturation/ambient_energy`.
Renderer is **GL Compatibility**, so SSAO/SSIL are unavailable; glow+fog+ACES are used
instead and unsupported settings are ignored rather than gated in code.
- World: scaled brick (uv 22,10), framed windows with per-window night emission
(`i % 3 != 2` lit), stone base course, door trim/handles, curb, lane dashes (the old
loop had `Vector3(i * 0.0, …)` and stacked every dash at x=0), street lamps use the
metal profile. The two flat planters that clipped the steps were removed.
- `tools/model_pipeline.py` builds `assets/models/generated/*.glb` (bollard, bench,
trash_bin, planter_box, newspaper_box, fire_hydrant) from box/cylinder specs;
`WorldBuilder._add_prop_model()` loads them with a procedural fallback and records
`loaded_models` / `fallback_props`. **Gotcha:** glTF `baseColorFactor` is LINEAR —
writing sRGB made every prop pastel; `srgb_to_linear()` is applied in `build_glb`.
**Gotcha:** a new/changed `.glb` needs `godot --headless --path . --import` before
`ResourceLoader.exists()` / `load()` sees it (test_generated_props fails otherwise).
- `model_pipeline.py concept` (OpenRouter, reads repo `.env`) writes per-prop concept
sheets to `assets/models/concepts/`; `dominant_colors()` centre-crops and weights by
saturation (a grey studio backdrop otherwise wins), `assign_roles()` maps colours by
hue/value. Delete a concept file to fall back to the catalogue palette.
- NPC avatars: `outfit_for(type, seed)` (pure) gives shirt/trousers/hair/skin +
accessory (cap, hood, backpack, sunglasses, lanyard) and height scale; `_arms` and
`_legs` now swing (`limb_swing`) while walking. Keep `Rig/Head` named for focus.
- Selection: `NpcFocus.rank_indices` (angle then distance) — `pick_index` = first.
`GuardController.cycle_target(direction)` walks nearest-first `nearby_candidates()` and
sets `target_pinned`, which makes `_update_target()` skip the cone. `clear_target()`
sets `focus_suppress_s = 0.75` so the cone does not instantly re-acquire the person.
New input actions `walk_slow` (Shift), `cycle_target_next` / `cycle_target_prev`
(wheel down / up). Right click with nothing open clears focus (Esc still pauses).
- HUD: `nearby_label` + `set_nearby_hint(count)` show `hud.nearby`; `hud.controls` and
`hud.targeting` mention the wheel and Shift.
- `tools/screen_designs.py`: catalogue now 19 screens (added wand_screening,
joke_shop_queue, studio_arrival, crowd_panic, responder_arrival,
body_camera_timeline, escalation_hud), new `list` command, `generate --only/--force`
(existing designs are kept by default so a draft run cannot clobber AI mockups),
and `judge --dry-run` no longer raises `KeyError: 'class'`.
- Screenshot tool leak: `capture_screenshots.gd` intermittently prints
`ObjectDB instances leaked at exit`. Verified against a `git worktree` of HEAD~ — the
baseline leaks more often (3/3 vs 1/3), so it is a pre-existing quit-timing artifact,
not a regression. Normal boots are clean.
- Access artifacts: `src/core/access_loadout.gd` builds from event `override_access` + venue policy.
Spawn signal is now `(profile, intent, npc_id, event)`. Recognition is not auto-attached to every
guest. `AccessVerifier.verify(..., already_screened=false)` so Joke Shop scoring uses
`controller.screened`. Joke Shop/Studio gained `sidewalk`/`steps_end` waypoints. Wand venues roll
`carried_items` per spawn (seeded). Tests in `src/test/tests/test_access_loadout.gd`.
- Wand shortcut is **`C`**, not `W` (`W` is walk). `radio_emergency` input map no longer includes W.
`hud.controls` / `hud.screen_first` updated.
- Control pass (historical; smoke-tested): cone focus via `src/game/npc_focus.gd`,
accel/decel, gravity only airborne, `input_locked` freeze, Esc no longer on the guard.
`NpcAvatar` in group `npc`; neck/hair/eyes/shoes; blue focus circle + `Label3D` name; `set_focus_visual`.
`src/game/ui/action_radial.gd` doughnut (LMB/Space open, A/D/arrows, Space/click confirm, Esc/RMB
cancel). Crosshair CanvasLayer. Emergency still `R` only. Number keys 1–6 when the wheel is closed.
- Named input actions now exist and are used: `action_menu`, `confirm`, `cancel` (Esc + RMB),
`inspect` (E), `wand` (C). The old unused `interact` action was removed.
- Runtime fixes from the smoke: `Head` is +1.6 above the guard root (root sits on the floor); guard
spawns in front of the door facing the street; building volume moved to z ∈ [-8,-4] so the door,
awning, and marquee are visible; step treads rise toward the door; `ShiftCoordinator` pulls the
event scheduler once per simulated second (was every physics frame → >1200 NPCs in seconds);
`GameManager.MAX_LIVE_NPCS = 16` caps live avatars.
- **Session shell (2026-10-31, historical):** the game boots into `StartMenu`
(`src/game/ui/start_menu.gd`, layer 20) instead of a live shift. `GameManager._on_venue_selected()`
builds the scenario and calls `_start_shift()`; `_shift_running` gates input and `_refresh_freeze()`.
Esc with nothing open now pauses (`PauseOverlay`, layer 18) → resume / restart / settings /
quit-to-menu. `SettingsPanel` (layer 22) writes `look_sensitivity` + `hud_font_scale` through
`Save.save_settings()` → `user://settings.json`; `Save.load_settings()` merges over
`Save.DEFAULT_SETTINGS` on boot and `GameManager._apply_settings()` pushes them into the guard/HUD.
Shift review "Run another shift" now calls `_show_start_menu()` (not a silent restart). `V` stays a
debug venue bind. Smoke: `src/test/smoke_session_shell.gd` (menu → shift → pause → settings → menu).
- **World follow-ups closed (2026-10-31):** skyline backdrop restored with `T_SKYLINE`
(`assets/textures/skyline.png`, regenerated RGBA with a transparent sky) and an
`TRANSPARENCY_ALPHA_SCISSOR` unshaded material (`_skyline_mat`); steps + landing are a
`StaticBody3D` with per-tread `BoxShape3D` colliders; marquee `font_size` 160 / outline 24; focus
circle (blue disc in front of the face) + name tag 48px; `NpcAvatar.ephemeral`/`lifetime_s` despawns routine traffic
(authored events stay). Guard spawn height verified on the landing (guard_y=0.84).
- **PLAN verbs (2026-10-31, historical):** the radial has 12 slices
(`Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio`).
`eye_contact` reuses the Level 1 reaction table via `_apply_interaction`. Recognition is a player
decision: `NPCController.player_recognized` + `IncidentRecorder.add_access_evidence`, and
`GameManager._artifacts_for()` prepends a `RECOGNITION` artifact only after the player declares
it. The body camera is a real flag (`GameManager.body_camera_on`, `Hud.set_body_camera`), off by
default, `B` toggles it, and a formal warning arms it and writes a `body_camera` incident event.
Blocking also calls `_station_between()` to physically place the guard 0.9 m from the NPC toward
the entrance. `_request_reason()` logs the NPC's seeded `dialogue_lines` pick.
- **NPC navigation was dead (fixed 2026-10-31):** avatars never moved in the live game, for three
stacked reasons. (1) `agent.target_position` was re-assigned every physics frame, which
invalidates the path so `get_next_path_position()` keeps returning the current point — only set
it when the target changes. (2) Nav path points sit on the navmesh plane (y=0) while bodies stand
at y≈0.5, so a 3D "are we there" test always looked 0.5 m away and the horizontal direction was
zero — compare horizontally. (3) `path[0]` is the old start point, so picking any point "far
enough away" can pick the point behind you and oscillate — pick the first point that is
**closer to the goal** than the current position. `_nav_try_frames`/`_move_direct` fallback is
still there.
- **Crowd reactions listen on the EventBus:** `CrowdSystem.apply_event()` emits
`event_bus.crowd_changed`; its own `changed` signal is never emitted. `GameManager._connect_crowd()`
therefore connects to `coordinator.event_bus.crowd_changed` (reconnect after every
`coordinator.setup()`, which recreates both crowd and event bus).
- **Crowd presentation:** `NpcAvatar.start_rubberneck(at)` (idle bystanders away from the door stop
and face the incident for 12 s) and `start_flee(from)` (panic: walk away at 2.4 m/s, then free
themselves). Someone `awaiting_interaction` within 3 m of the door is "engaged" and does not
rubberneck. Use `position` (not `global_position`) for that distance check so it also works in
headless tests outside the tree.
- **Perf:** a busy 16-NPC entrance measured ~1.2 ms/frame (824 fps with vsync off, 800 samples),
so LOD/culling is not warranted at `MAX_LIVE_NPCS = 16`. Re-measure before raising the cap.
- **Incident recording was dead:** `IncidentRecorder.start_incident()` was never called, so every
`add_event`/`add_access_evidence` silently no-opped and the review always showed 0 incidents.
`_begin_incident()` now starts one per focused NPC (`has_open_incident()` guard), verbs call
`_ensure_incident()`, and `ShiftCoordinator._finish_shift()` closes leftovers as `unresolved`
before building the review.
- **Editor-ready venues (2026-10-31):** `tools/export_venues.gd` (a SceneTree script) saves the
three factory scenarios to `content/venues/<id>.tres` with `ResourceSaver`; the `.tres` files
round-trip through `ResourceLoader` (events, profiles, policy, shift phases, door lines,
locations). `test_venue_resources.gd` also fails if the exported files are missing or stale
relative to the factory, so re-run the export after changing `ScenarioFactory`.
- **Vision judge (2026-10-31):** `python3 tools/screen_designs.py judge` sends the capture plus
its concept mockup to a vision model (`DEFAULT_JUDGE_MODEL = google/gemini-2.5-flash-lite`,
override with `--model`; `openai/gpt-4o-mini` also works). Reads the repo `.env` via
`_load_env_file()` (never overrides real env). Verdicts are classed `match` / `review`
(mismatch below `--fail-confidence`, default 0.6) / `diff` (confident mismatch → exit 1) /
`unknown`. `--dry-run`, `--json`, `--only` supported. The prompt explicitly ignores art style,
because the references are stylised mockups; the live run gave 3 match, 3 review, 0 diff.
Python tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs`.
- **String formatting safety (2026-10-31):** `Strings.placeholder_count()` counts `%` specs
(ignoring `%%`), `fmt()` warns and returns the template when the arg count does not match
(instead of raising an engine error), and `validate()` lints the table for trailing `%`,
unsupported conversions, and mixed positional/sequential placeholders. Tests cover all three
plus a formatting pass over every placeholder key with type-correct dummy args.
- **Authored scenario content (2026-10-31):** events can now carry `hint` (a string key logged
when the avatar spawns), `extra_spawns` (celebrity arrivals pull in tourists/influencers via
`_spawn_npc` with `kind: routine`), and `refuses_screening` (copied to
`NPCController.refuses_screening`; `_toggle_wand_screening` logs `wand.refused`, records a
`wand_refused` event, applies a small prop hit, and does not open the panel). `film_boundary`
is a new intent that maps to `steps_end` so the influencer stands past the posted line.
- **Accessibility (2026-10-31):** escalation text is `"<name> [###--] L3/5"` via
`Hud.escalation_meter(level)` (pure, tested) so the cue survives grayscale. HUD font scale uses
`add_theme_font_size_override` on the core labels (`set_font_scale`), **not** `Control.scale`,
which pushed the right-anchored clock label off-screen. Settings load before `_build_ui()`, so
`_ready()` re-applies player settings after the HUD exists.
- **Audio (2026-10-31):** `AudioFeedback.start_crowd_bed()` builds a 4 s looping `AudioStreamWAV`
(one-pole-filtered noise + 68/103 Hz drone, seeded) and `set_crowd_level(mood)` maps 0..1 to
-34..-12 dB. `play_radio_open/close` push short filtered-noise bursts. All procedural; with a
dummy (headless) audio server the calls are no-ops. `_free_player()` returns null when `_ready`
has not run (tests), and callers handle null.
- **NPC theater (2026-10-31):** `NpcAvatar.theater_pose()` maps `intent` + `arrived` to
idle/sit/photo/film/perform. Pure helpers `crouch_for_pose`, `prop_lift_for_pose`,
`sway_for_pose` keep the math testable. Sit lowers the rig by 0.34 and swings the legs forward
(negative X pitch; positive looked like kneeling). `_photo_prop` is set for influencer/tourist
cameras, `_sign_prop` for protester/performer boards; their base Y values are captured at build
time so the animation offsets from rest.
- **Body-camera timeline (2026-10-31):** `src/game/ui/event_timeline.gd` flattens
`review.incidents.incidents[].events` into one time-ordered list (`flatten`), formats each row
with `describe`, and steps with Left/Right or the buttons. `ShiftCoordinator.started_at_ms`
gives the review an absolute start for mm:ss stamps. `GameManager._open_timeline` hides the
review dim (two stacked overlays made the screen nearly black) and `_timeline_from_review`
restores it on close. Smoke: `src/test/smoke_timeline.gd`.
- **Radio dispatch was dead after the first shift:** `coordinator.setup()` builds a new
`RadioSystem`, so the `responder_arrived` connection made in `_ready` was lost on every venue
switch/restart (responders never resolved anything). `_connect_radio()` now re-wires it next to
`_connect_crowd()` after every setup. Same class of bug as the crowd signal.
- **Emergency aftermath:** `ShiftCoordinator.responder_outcomes` / `investigations` feed the review.
`evacuation` (emergency call, low danger, no weapon) locks the door, makes every NPC flee, marks
them `leave_requested`, and applies a `security_failure` hit. An emergency placed while
`current_npc_truth` has no weapon and `danger_level <= 1` appends an investigation note, records
`investigation_opened`, and applies `false_report` -6.
- **Skyline backdrop (2026-10-31):** `PlaneMesh` defaults to a **horizontal** XZ plane — set
`mesh.orientation = PlaneMesh.FACE_Z` or the "backdrop" renders as a band overhead. The skyline
is now a ring of four 400x60 planes at 200 m (center y=30, so building bases meet the ground),
`TEXTURE_FILTER` default, `TRANSPARENCY_ALPHA_SCISSOR` with threshold **0.5** (0.3 left faint
mip dashes across the sky), unshaded with a lifted albedo (`Color(2.6,2.7,3.0)`) so it is not
pure black. Ground is 500x500 so no sky gap shows under the buildings. The `skyline.png` import
needs `mipmaps/generate=true`.
- **Asset gotcha:** `assets/textures/game_ready/` is *not* imported (no `.import`). Use the
imported `assets/textures/skyline.png` path. Untracked duplicate folder should not be referenced.
- Host sign-in follow-up is **done**: `REQUIRE_SIGNIN_VIEW = false` in `/etc/gitea/app.ini`
(backup `app.ini.bak-*` alongside), Gitea restarted; anonymous repo page/API return 200 and
release assets download in full. Reverse by setting it back to `true` and restarting.
- **Release publishing was broken since v0.2.2 (fixed 2026-10-31):** `publish_release.sh`
only built `AUTH` in the `GITEA_TOKEN` branch; the `~/.git-credentials` branch set `CRED`
and then used unbound `${AUTH[@]}` under `set -u`, so every API call failed and the tag
job ended "could not create or find release". The repo also has **no** `GITEA_TOKEN`
secret (`/actions/secrets` is empty) and the runner has no git credentials. Fixes: the
script sets `AUTH=(-u "$CRED")`; the workflow falls back to `github.token`
(`secrets.GITEA_TOKEN || github.token`) — but `github.token` is **not** provided by this
Gitea/act runner, so the env dump showed no token and the v0.2.10 tag build failed again.
Final fix: `publish_release.sh` falls back to the **local git extraheader** that
`actions/checkout` writes (`git config --local --get-regexp '^http\..*\.extraheader$'`),
which carries the runner's basic-auth credentials for the same server. Verified locally by
adding that extraheader and publishing v0.2.10 (all five artifacts). **v0.2.9 and v0.2.10
are published**; v0.2.4–v0.2.8 tags have no artifacts. Verify the CI path on the next tag.
- **CI publishing verified (2026-10-31):** tagging v0.2.12 produced a release with all five
artifacts automatically (no repo secret), confirming the checkout-extraheader fallback.
- **Host access (2026-10-31):** `root@192.168.88.30` now works from this workstation (key auth),
host name `gitea`, Gitea 28.2.0. Maintenance applied: `apt-get clean`, journald capped to
`SystemMaxUse=300M` (`/etc/systemd/journald.conf.d/99-size.conf`, journal ~133M), disk at
~7.6G free of 16G.
- **Host disk resolved (2026-10-31):** the Gitea VM root filesystem was expanded from 7.8G to
16G (`/dev/mapper/pve-vm--500--disk--0`, now ~7.8G free). The pruned **v0.2.0** and **v0.2.9**
releases were restored from the local `build/` artifacts and verified (4 and 5 assets).
`git.allucanget.biz` runs on this host as user `zwitschi`; root@192.168.88.30 is **not**
reachable from this workstation (publickey denied; password auth closes the banner), so host
maintenance must go through a `zwitschi` login with sudo.
- **Host disk full (2026-10-31, historical):** the v0.2.11 tag build created release id 27 and logged the
uploads, but they failed with `HTTP 500 Attachments.Save: write … no space left on device`
(`/dev/mapper/pve-vm--500--disk--0`, 7.8G, 100%, 0 free). `git push` then failed too
("unpacker error"). Unblocked by deleting the superseded **v0.2.0** and **v0.2.9** releases
via the API (~565 MB of assets), then publishing v0.2.11 locally with the fixed script
(verified 5 assets). Remaining ~150 MB is tight — the next release needs host cleanup on
192.168.88.30: `sudo journalctl --vacuum-size=100M` (journal 837 MB), `sudo apt-get clean`
(91 MB), clear `/var/lib/gitea/data/attachments/tmp`. SSH works as `zwitschi`; `sudo -n`
needs a password. **Releases now:** v0.2.11, v0.2.10, v0.2.1 (5 assets each); v0.2.0 and
v0.2.9 entries were pruned. PLAN.md + ADRs 0007–0010 still describe the intended UX.
- **Release v0.2.1 is LIVE** (2026-10-31): CI run 1110 built the NSIS installer + AppImage
and published — installers/AppImage are on the releases page. Releasing = bump version → tag → push.
- NSIS gotchas (validated in a local ubuntu:24.04 docker repro): makensis uses `-D` defines
(never `/D`), resolves relative `-DICON`/`-DARTIFACT` against the **script directory** so CI
passes `$PWD`-absolute paths; installed exe must be space-free (`gig-line.exe`); ICO must be
classic BMP entries (ImageMagick `icon:auto-resize=48,32,16`; PIL emits PNG-compressed ICOs NSIS
cannot load).
- appimagetool runs system `appstreamcli` if present (strict, exit 3 on warnings) — project
metadata now passes `--pedantic`.
- Distribution facts (verified via Gitea API + host creds): repo is **public**, account is
admin; `v0.2.1` has all 5 assets; the installers were initially missing from the release
because `publish_release.sh` uploaded only portables — now fixed (uploads installer +
AppImage; falls back to `~/.git-credentials` when no `GITEA_TOKEN`).
- Host sign-in: instance `[service] REQUIRE_SIGNIN_VIEW=true` blocks anonymous browse/download
(not a repo/API setting). `~/.git-credentials` holds the Gitea creds (basic-auth usable);
`.env` only holds `OPENROUTER_API_KEY`.
- **Release v0.2.0 was LIVE**: pushing the `v0.2.0` tag made Gitea Actions run the build job
and publish automatically (host has the `GITEA_TOKEN` secret). Tag page and `/releases` are
public (HTTP 200). Caveat: anonymous **asset downloads** redirect to login on this host
(host setting — tracked in TODO). Future releases = bump `config/version`, `git tag vX.Y.Z`,
push. No local token needed.
- Core simulation, three scenarios, headless tests (**57 passing**), and a first-person prototype with interaction HUD, access inspection, radio (assist + emergency hold-to-confirm), debug overlay, and shift review are implemented and green.
- Core simulation, three scenarios, v0.2.1 installers/AppImage, first-person prototype (inspect/wand/radio, shift review, Tab targeting, V venue cycle) are on local `main`. Tagged v0.2.1 was 57 tests; this working tree is **74/74**.
- NPC avatars now walk to per-intent waypoints (`VenueScenario.waypoint_for_intent`): enter/trespass/deliver/arrive → entrance, photo/film/perform/wander/roam → sidewalk, sit → steps_end. `NpcAvatar.setup` takes an explicit `door_position`.
- Returning-trespasser behavior: `NpcProfile.return_probability` (trespasser 0.6, mental-crisis 0.4, others 0). `NPCController._maybe_schedule_return()` moves a directed-away NPC to `LOITERING_DISTANT`; `tick_return(delta)` fires after 20-60s and transitions back to `APPROACH`. `NpcAvatar._advance_fsm` re-targets the door on return.
- Perception pass: `NPCController.perceive_guard(distance)` returns `{noticed, band, reaction}` with bands `unaware` (>6m), `aware` (2-6m), `near` (<2m). Near + temperament ≥0.6 → ARGUE, else COMPLY. `GameManager._perception_pass()` feeds real guard distance each physics tick and logs first-time notice.
- Responder dispatch: `RadioSystem` tracks `pending_calls` and decrements `arrival_delay_s` in `tick`; emits `responder_arrived` when a call's delay elapses. `RadioSystem.responder_outcome(call)` maps danger/weapons → command/restraint/evacuation/arrest. `GameManager._on_responder_arrived` spawns a `RESPONDER` avatar at the entrance and logs the outcome. HUD shows an emergency-radio cooldown label via `_update_radio_cooldown`.
- Wand screening (Joke Shop): `WandScreener` (new `src/core/wand_screener.gd`) classifies carried items as prohibited (`weapon/knife/gun/blade`) vs false-alarm (`keys/belt/phone/coins/watch/buckle`) vs clear. `NpcProfile.carried_items` holds guest items. `GameManager` binds `W` to `_toggle_wand_screening` and `A`/`D` to `decide_wand`; `decide_access` blocks admission at wand venues until `controller.screened` is true. `VenueScenario.door_staff_lines` holds venue-specific door-staff dialogue (Joke Shop has id/ticket/wand/refusal/prohibited lines).
- Wand screening (Joke Shop): `WandScreener` classifies carried items as prohibited vs false-alarm vs clear. `NpcProfile.carried_items` is filled at spawn on wand venues. `GameManager` binds **`C`** to `_toggle_wand_screening` and `A`/`D` to `decide_wand` while inspect/wand is open. `decide_access` still blocks admission until `controller.screened` is true, and `verify` now takes that flag. Door-staff lines log through GameManager.
- Design change (2026-10-31): the selectable dialogue tone-of-voice system was **removed**.
`Rules.Tone`/`TONE_NAMES`, `NpcProfile.tone_modifiers`, and the tone param on
`NPCController.roll_reaction/apply_player_action` no longer exist; `NpcProfile.dialogue_lines`
@@ -59,6 +587,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- Assets pipeline: `python3 tools/generate_textures.py` produces all authored PNGs under
`assets/textures/` (≤1024px, pow2); regenerate after changing the script, then run
`godot --headless --import` before tests. 57 headless tests guard the pipeline.
- General image pipeline: `python3 tools/asset_pipeline.py` defaults to
`openai/gpt-5-image-mini`, requests supported 1:1/3:2 sources, then fits each role's target.
It provides crop, resize, tile edge blending, alpha-glyph recoloring, and role-aware audits.
Screenshot auditing checks 1280x720 minimum, 16:9 aspect, and contrast; visual composition
remains a manual check. Python tools require Pillow from `requirements-tools.txt`.
- AI texture mode: `python3 tools/generate_textures.py --ai` generates textures via the
OpenRouter image API (`POST https://openrouter.ai/api/v1/images`, model
`bytedance-seed/seedream-4.5` by default, base64 PNG in `data[].b64_json`). Requires
@@ -67,12 +600,14 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
default batch. Output is downscaled to ≤1024px. NOTE: seedream rejects sizes below
3,686,400 px (1920×1920), so the script requests `GEN_SIZE=2048` and downscales — do not
lower `GEN_SIZE` below 1920. Verified end-to-end 2026-10-31 (3 textures generated).
`AI_TEXTURES` now covers all 15 game textures (world surfaces, awning, skyline, vignette,
4 HUD icons, 5 access cards) with a `transparent` flag per entry. NOTE: seedream-4.5
ignores `background: transparent` and returns fully opaque images (alpha=255), so the
`ai/` icons/vignette/skyline are opaque — they are NOT drop-in replacements for the
authored transparent PNGs. Use a transparency-capable model (or post-process) if alpha
is needed.
Legacy `--ai` remains on Seedream 4.5 and still ignores `background: transparent`; do not
use it for alpha-dependent assets. Gemini 3.1 Flash Image advertised broad ratios but the
current provider rejected authentication. The validated role-aware model is GPT-5 Image Mini.
- Runtime assets: all 15 loaded world/HUD/card textures now live in
`assets/textures/ai/game_ready/`; each passes role audit. Generated tile edges are blended,
icon alpha masks are recolored white where needed, and the vignette uses the existing
deterministic generator because the API returned an empty transparent image. The six
`docs/screenshots/` captures were regenerated after wiring and pass screenshot audit.
- Detailed graphics: textured facade/awning/skyline, humanoid NPC rigs + props + walk bob +
attention head-turn, inspection artifact cards, mode icons, escalation vignette, body-cam
overlay (REC + shift clock). Screenshot set regenerated (`docs/screenshots/`, day + night).
@@ -80,6 +615,17 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- NOTE: collaborators occasionally add uncommitted work to this shared tree (e.g. the radio
easter egg); check `git status` before committing and leave foreign changes untouched unless asked.
- Screen-design references (`tools/screen_designs.py`, tests in `tools/test_screen_designs.py`):
`generate` turns the screen descriptions in `docs/` (README captions, ADRs 0007–0010,
PLAN, architecture) into reference images under `docs/screens/designs/` — `--draft`
draws offline Pillow wireframes, `--ai` generates realistic 3D scenes / in-game UI via
OpenRouter (default model `openai/gpt-5-image-mini`). Screens are tagged `scene` or
`ui`; prompts get a shared art-direction suffix, and requests use a 3:2 landscape
source cropped to 16:9 (not a square centre-crop). `compare` checks `docs/screenshots/*.png` for aspect/size/
blank-frame and coarse layout/palette/detail drift. Drift distances are warnings, not
failures: a mockup never matches a real render, so semantic composition is a follow-up
vision-model judge (tracked in TODO).
## Known Issues / Notes
- Reference cycles are fixed: `FSM` holds its owner via `WeakRef`, `ShiftCoordinator` is
@@ -92,7 +638,21 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- Steps are visual-only (no collider) so NPCs can path to the entrance; the guard spawns on the landing so it never needs to climb.
- Persistence tests write into the real `user://shift_results.json` (harmless local pollution of the demo history).
- macOS export required `rendering/textures/vram_compression/import_etc2_astc=true` in `project.godot` (universal build demands ETC2 ASTC enabled).
- Joke Shop `door_staff_lines` authored but not yet surfaced in the prototype UI (tracked in TODO).
- Joke Shop `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI.
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
- Headless suite is **229/229**; the windowed smokes, screenshot compare and judge pass. Remaining visual follow-ups are in `TODO.md`.
- Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args).
- GDScript inference trap that broke the main scene: a `var` typed as a base class
(`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a
parse error. Type it as the script type (`ActionRadial`) or annotate the local explicitly.
`--check-only --script` cannot see the `Save` autoload; use a real boot (`--quit-after 180`) to
verify a full-scene compile.
- Do not snapshot right after changing a transform in the same frame: `capture_screenshots.gd`
needs a state/frame gap or the capture uses the old camera orientation.
- `EventScheduler` probabilities are per simulated second, not per call. Never poll it per frame.
- Screenshot staging lives in `src/test/capture_screenshots.gd` (guard at z≈10.5 facing the door).
`docs/screenshots/` copies are manual: copy from `user://screenshots/`.
## Open Questions
@@ -100,8 +660,19 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- Localization target languages beyond English.
- macOS notarization approach for broad distribution.
## Plan vs TODO Gaps (evaluated 2026-10-31, all resolved)
## Plan vs code
All previously identified gaps are now implemented and covered by tests/smoke items: PROTESTER archetype, door lock/secure, flashlight, CALL_SOMEONE reaction, LATE phase fix, shift-result persistence, NavigationAgent3D movement, and JSON-backed localization. Remaining backlog lives in `TODO.md`.
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
2026-11-01 the playable build covers all three venues, controls, access/radio/evidence/review,
the Blender asset library, the realism pass, and the visual-alignment pass. `TODO.md` holds only
the realism follow-ups and optional future ideas. The project uses a custom headless runner, not
GUT, and Gitea Actions, not GitHub Actions.
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
## Uncommitted Workstreams
- **None (2026-11-01).** The NPC detail pass, the realism follow-ups and the flashlight beam
decision are committed together with the portrait capture and the refreshed screenshots.
Keep the rule from `AGENTS.md`: check `git status` before committing, and never revert
another contributor's in-flight files.
+117 -391
View File
@@ -1,461 +1,187 @@
# Game Plan: Step Line
# Game Plan: Gig Line
## Premise
You play a private security guard stationed at the top of the steps outside a fictional building on Hollywood Boulevard. Your job is to keep the entrance and steps clear while dealing with tourists, trespassers, performers, protesters, intoxicated pedestrians, and occasional determined intruders.
You are a private security guard working the entrance of a fictional Hollywood Boulevard building. You keep the steps and doors clear, check who is allowed in, and deal with tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in mental-health crisis, and rare determined threats.
The game is about **judgment, communication, and escalation management**, not simply attacking anyone who approaches.
The game is about **judgment, communication, access verification, and proportionate escalation**, not about attacking everyone who approaches.
---
## Design Pillars
- **Lowest effective response wins.** Observation, a gesture, or a clear direction should solve most situations.
- **People are not labels.** Clothes, props, credentials, and behavior tell the player what they need; internal NPC types stay in the debug overlay.
- **Every decision has a record.** Incidents, evidence, radio reports, and outcomes feed the shift review.
- **Emergency help is a last resort.** Responders handle crises the guard cannot safely manage; calling them has costs.
- **Readability beats raw fidelity.** The GL Compatibility renderer, clear silhouettes, the focus ring, and the HUD stay legible.
## Core Gameplay Loop
1. Watch the stairs, entrance, and sidewalk.
2. Identify why each person is approaching.
3. Give verbal warnings and directions.
4. Escalate only when necessary.
5. Call for assistance in rare dangerous situations.
6. Maintain public safety, building security, and your employer’s reputation.
1. Watch the entrance, steps, queue, and sidewalk.
2. Work out why each person is approaching.
3. Use a gesture or short direction before escalating.
4. Verify authorized access when the venue requires it.
5. Escalate only when the person refuses, forces entry, or presents a genuine threat.
6. Record the incident and call for help when it is justified.
7. Review the shift: safety, judgment, accuracy, crowd reaction, and reputation.
People should not all behave the same way. Some are confused, some are rude, some are filming, and a very small number are deliberately attempting to enter.
A shift lasts roughly 10–15 minutes. Most people comply when approached calmly.
## Authorized Access
## Access and Verification
Some people are authorized to enter the building. The security guard must verify them before granting access.
Some people are authorized to enter. The guard may admit them by:
An authorized person can be admitted if either:
- recognizing a person the guard already knows; or
- verifying a venue-appropriate artifact: wristband, ID, ticket, credential, or guest list.
- The guard already knows and recognizes them.
- The person presents a valid event or staff wristband, which the guard checks before opening the entrance.
Artifacts can be valid, expired, counterfeit, mismatched, missing, or ambiguous. The player inspects the details the venue asks for instead of judging by appearance.
The wristband check should require the player to inspect details such as its color, pattern, date, or event marking. NPCs may have valid wristbands, expired wristbands, counterfeit wristbands, or no wristband at all.
Decisions are admit, redirect, hold, or refuse. Incorrectly denying an authorized person hurts reputation; admitting an unauthorized person is a security failure. Recognition is a player decision and is recorded as evidence.
The player should avoid relying on appearance or assumptions. A familiar person may still need verification if the building’s current policy requires it, while an unfamiliar person with a valid wristband may be admitted after a successful check. Incorrectly denying an authorized person harms the building’s reputation; admitting someone without authorization creates a security failure.
## Escalation Model
---
| Level | Player response | Intent |
| --- | --- | --- |
| 0 | Observation | Watch harmless behavior; intervene only when needed. |
| 1 | Simple deterrence / polite direction | Raise a hand, make eye contact, point to the sidewalk or check-in area, shake the head, give a short instruction. |
| 2 | Formal warning | State the rule and ask for compliance; optionally arm the body camera. |
| 3 | Blocking and positioning | Stand between the person and the door, secure the entrance, keep distance and an escape route, ask for their reason. |
| 4 | Radio for security assistance | Request a second guard, supervisor, building management, or other support. |
| 5 | Emergency response | Hold and confirm the emergency call only for an immediate threat such as a weapon, assault, forced entry, or serious threat to occupants. |
## Escalation System
At Level 2 an NPC may leave, argue, pretend not to understand, record the guard, call someone, or move closer. At Level 3 the goal is to deny access **without starting a fight**. Physical contact creates legal, employment, and public-relations consequences.
## Level 0: Observation
The guard watches NPC behavior and decides whether intervention is needed.
Examples:
- Someone taking a photograph from the sidewalk
- A tourist reading a map
- A person sitting briefly on the edge of the steps
- Someone approaching the entrance with a delivery package
The player should avoid interrupting harmless behavior. Unnecessary confrontations reduce the guard’s performance rating.
---
## Level 1: Simple Deterrence and Polite Direction
The player can begin with a simple, nonverbal deterrent before speaking. These actions are quick and low-pressure, making them useful when someone is only approaching the steps casually or appears unsure.
Possible actions include:
- Raise an open hand to signal “stop” or “please wait”
- Make eye contact
- Point toward the public sidewalk
- Gesture toward the wristband-check area
- Shake the head gently when someone approaches a restricted entrance
If the person continues, the player uses a short verbal prompt.
Example commands:
- “Please keep the stairs clear.”
- “The entrance is for authorized guests only.”
- “You can’t remain here.”
- “Please move back to the sidewalk.”
At this stage, most NPCs comply immediately.
---
## Level 2: Formal Warning
If the person remains on the steps or continues toward the entrance, the player issues a formal warning.
Possible dialogue:
> “This is private property. You have been asked to leave. Please move away from the entrance now.”
The player may activate a visible security light or body camera. This creates a record of the interaction and can discourage repeat offenders.
At this level, the NPC may:
- Leave
- Argue
- Pretend not to understand
- Start recording the guard
- Call someone else
- Move closer to the door
---
## Level 3: Blocking and Physical Positioning
The guard moves into a defensive position between the NPC and the building.
Gameplay actions:
- Stand in front of the entrance
- Point toward the public sidewalk
- Lock or secure the door
- Request the person’s reason for entry
- Keep distance while maintaining a clear escape route
- Use a flashlight or radio warning tone at night
The player should not automatically shove or strike people. Physical contact creates legal, employment, and public-relations consequences.
This level is about **denying access without creating a fight**.
---
## Level 4: Radio for Security Assistance
If the person refuses to leave, attempts to force the door, threatens staff, or repeatedly returns, the player uses the radio.
Possible assistance:
- A second security guard
- Building management
- Police dispatch
- Nearby security personnel
The radio call takes time, so the player must continue monitoring the person while waiting. The player can choose what information to report:
- Location
- Description
- Behavior
- Whether weapons have been seen
- Whether anyone is in immediate danger
Incorrect or exaggerated reports damage the guard’s credibility.
---
## Level 5: Emergency Response
This is reserved for immediate threats, such as:
- A person displaying a weapon
- An attempted forced entry while people are inside
- An assault on the guard or another person
- A serious threat against occupants
- A violent struggle that cannot be safely disengaged
The player presses and holds a clearly marked **Emergency Radio** button. A confirmation prompt prevents accidental activation.
The call brings an off-duty law-enforcement officer or armed emergency responder to the location. This character has greater authority and can control a dangerous situation, but the call has serious consequences:
- The building may be evacuated
- The street may be closed
- The guard’s actions are reviewed
- The building loses reputation
- The player may face an investigation if the call was unjustified
- The officer may use force only when the game’s threat assessment supports it
The officer should not function as a convenient “stronger attack.” Their role is to handle a crisis the player cannot safely manage. In most scenarios, their arrival results in commands, restraint, evacuation, or arrest rather than automatic gunfire.
---
At Level 4 the report matters: location, description, behavior, weapons seen, and immediate danger. Exaggerated reports damage credibility. At Level 5 the call may evacuate the building, close the street, trigger a review, cost reputation, or open an investigation if unjustified. Responders may command, restrain, evacuate, or arrest; they are not a routine stronger attack.
## NPC Behavior Types
## Tourists
- Take photos
- Sit on steps
- Wander toward restricted doors
- Usually respond well to polite directions
## Street Performers
- May block stairs
- May become argumentative if moved
- Can be redirected to a permitted area
## Influencers and Filmmakers
- Film the guard
- Test boundaries for content
- May accuse the guard of overreacting
- Create public-relations risk
## Confused Visitors
- Have appointments or directions
- May genuinely believe the building is public
- Reward patient explanations
## Intoxicated Pedestrians
- Move unpredictably
- May ignore instructions
- Require additional distance and assistance
## Trespassers
- Repeatedly test entrances
- Use distractions
- May return after being told to leave
## Violent Threats
- Rare
- Escalate quickly
- Trigger the emergency response system only when their behavior indicates immediate danger
## 51/50s
Crazy Homeless Individuals may be referred to as "51/50s" in the context of the game, indicating a person who may be experiencing a mental health crisis and could pose a potential threat to themselves or others.
- May loiter near entrances
- Can be unpredictable in behavior
- Often respond poorly to aggressive confrontation
- Require careful de-escalation and patience
---
- **Tourists:** take photos, sit on steps, drift toward restricted doors; usually respond well to polite directions.
- **Street performers:** may block stairs or argue when moved; can be redirected to a permitted area.
- **Influencers and filmmakers:** film the guard, test boundaries, and create public-relations risk.
- **Confused visitors:** may have appointments or genuinely believe the building is public; reward patient explanation.
- **Intoxicated pedestrians:** move unpredictably, ignore instructions, and need extra distance or assistance.
- **Trespassers:** test entrances, use distractions, and may return after being told to leave.
- **Violent threats:** rare, escalate quickly, and justify emergency response only when behavior shows immediate danger.
- **People in mental-health crisis:** may loiter, behave unpredictably, and respond poorly to aggression; require patience and careful de-escalation. The setting may refer to a 51/50 hold, but the game should present the person respectfully, not as an automatic threat.
## Special Systems
## Reputation Meter
### Reputation
The player is evaluated on:
The shift evaluates peaceful resolutions, response time, radio accuracy, unnecessary confrontations, public complaints, property damage, entrance security, and whether assistance was justified. Calling armed help too often scores poorly even when no one is hurt.
- Number of incidents resolved peacefully
- Response time
- Accuracy of radio reports
- Number of unnecessary confrontations
- Public complaints
- Property damage
- Whether the entrance remained secure
### Evidence and Body Camera
A player who calls armed assistance too often should score poorly, even if no one was injured.
Interactions are recorded automatically. The body camera is a real state the player can toggle; a formal warning also arms it. The shift review uses the recorded events and evidence to show whether the response was justified.
## Evidence and Body Camera
### Crowd Reaction
Interactions are automatically recorded. After each shift, the player reviews incidents and sees whether their escalation was justified.
Nearby pedestrians react to the guard. Calm professionalism attracts little attention; aggression draws a crowd; visible weapons or responders can cause panic. Good de-escalation keeps the boulevard moving.
This encourages players to think before acting.
### Radio Limits
## Public Crowd Reaction
Nearby pedestrians react to the guard’s behavior:
- Calm professionalism attracts little attention
- Aggressive behavior creates a crowd
- Visible weapons or emergency responders cause panic
- Good de-escalation keeps the boulevard moving normally
## Limited Radio Battery or Cooldown
The emergency radio should not be infinitely available. A call has a cooldown and may require the player to give a short report first.
---
The emergency radio has a cooldown, and the call requires a short report. Assistance takes time to arrive, so the guard must keep monitoring the situation.
## Shift Structure
Each shift lasts 10–15 minutes.
- **Early:** harmless tourists, delivery workers, people resting on the steps.
- **Midday:** heavier crowds, more entry attempts, more filming.
- **Evening:** lower light, more intoxicated pedestrians and aggressive groups.
- **Late:** a rare serious incident may test whether the player recognizes a real threat without overreacting to ordinary behavior.
### Early Shift
## Outcomes
Mostly harmless tourists, delivery workers, and people sitting on the stairs.
A successful shift keeps the building secure, resolves most incidents without force, keeps public access clear, makes justified emergency calls, avoids bystander injuries, and follows policy.
### Midday
A poor shift involves excessive force, confronting innocent people, ignoring a real threat, false reports, allowing unauthorized entry, causing a crowd or panic, or behaving recklessly even if the door stayed locked.
Crowds become heavier. More people attempt to enter the building or film interactions.
## Venues
### Evening
### Hollywood Boulevard Building
Lighting decreases. Intoxicated pedestrians and aggressive groups become more common.
The original scenario: a building entrance with steps, sidewalk, street lamps, and a restricted door. Tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in crisis, and rare violent threats approach from the boulevard.
### Late Shift
Authorized people are recognized or present a valid wristband. Wristbands vary by color, pattern, date, and event marking, and may be valid, expired, counterfeit, or missing.
A rare serious incident may occur, testing whether the player recognizes a genuine threat instead of overreacting to ordinary behavior.
### The Joke Shop
---
A comedy-club entrance. The door person checks IDs, tickets, reservations, guest-list status, and screens guests with a handheld wand under venue policy.
## Win and Loss Conditions
The wand does **not** identify a weapon automatically. Keys, belts, and phones can trigger false alarms; the player must ask the guest to identify the item and apply procedure. A guest with valid credentials can still require assistance if they threaten staff or patrons.
## Successful Shift
Typical events:
- Building remains secure
- Most incidents are resolved without physical force
- Public access remains clear
- Emergency calls are justified
- No bystanders are injured
- The player follows company policy
- a busy weekend show creates a long line;
- a performer arrives through the guest entrance;
- a guest has a reservation but no ID;
- a fake or borrowed ID appears;
- a wand alert comes from keys or a belt;
- a guest refuses screening but insists on entering;
- someone tries to follow an authorized person through the door;
- a late audience member arrives during the performance.
## Poor Performance
Joke Shop escalation follows the common ladder: direction, explanation, repeat request, refusal, manager/security assistance, emergency only for immediate danger.
- Excessive use of force
- Repeatedly confronting innocent people
- Ignoring a real threat
- Making false radio reports
- Allowing repeated unauthorized entry
- Causing a crowd or panic
### Hollywood Boulevard Late Night TV Studio
A shift can be technically “secure” but still receive a poor rating if the guard behaves recklessly.
A busy studio entrance with audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, influencers, and the same range of street behavior as the building scenario.
---
Staff and guests are recognized or present a wristband, credential, ticket, guest-list confirmation, or other approved marker. The player keeps the steps clear, protects arrivals and equipment, manages filming boundaries, watches for tailgating, and calls for help only when there is immediate danger.
## Venues and Scenarios
Typical events:
The game should support multiple venues, each with its own access rules, crowd patterns, equipment, and escalation procedures. The core gameplay remains the same: observe, verify access, communicate clearly, and use the lowest effective level of escalation.
- a crowd gathers for a celebrity arrival;
- tourists block the entrance for photographs;
- a performer sets up in the audience queue;
- an influencer ignores the filming boundary;
- a confused visitor believes the studio is open;
- an intoxicated pedestrian approaches the stage door;
- a trespasser blends into an audience group;
- an authorized guest arrives without a wristband;
- someone presents a counterfeit credential;
- a violent threat appears during a crowded arrival.
## The Joke Shop
## Presentation and Controls
**The Joke Shop** is a comedy club scenario where the player works as a door person at the entrance. The player is responsible for checking identification, confirming that guests are attending the correct show, and screening guests for weapons with a handheld security wand.
The game is first-person: WASD and mouse look, a focus cone on nearby people, a billboarded focus circle, `Tab`/mouse-wheel cycling for aim-free selection, and a radial action menu opened with left click or `Space`. `Shift` is precision walk. Gamepad input is supported, and keys/buttons can be remapped in Settings.
### Primary Responsibilities
The action wheel prioritizes de-escalation: Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio. Emergency stays on `R` and is not a wheel slice. `W` is walk, never the wand; `C` is the optional wand shortcut.
- Check each guest’s government-issued ID where required.
- Confirm that the guest meets the venue’s age requirement.
- Verify that the ID appears valid and belongs to the person presenting it.
- Check tickets, reservations, or guest-list status.
- Use the handheld wand according to venue policy.
- Direct guests through the entrance without blocking the sidewalk.
- Refuse entry when a guest has an invalid ID, no valid admission, or a prohibited item.
The visual direction is readable stylized realism:
### Guest Outcomes
- **Blender library:** 8 skinned NPC variants, 18 gear pieces, and 20 street/venue props, with baked albedo/normal/AO/ORM maps, a manifest, and procedural fallbacks. `Rig/Head` stays the focus anchor.
- **Realism pass:** AI-assisted per-phase skyline panoramas, v2 surface tiles with normal maps, wet asphalt at night, a warm day haze, phase-specific sun/exposure/fog, a visible flashlight beam, and denser foliage.
- **References and QA:** runtime UI art lives in `assets/images/ui/`; reference/art-direction sets live in `docs/screens/designs/` and `docs/screens/concepts/`; captures live in `docs/screenshots/`; realism runs live in `docs/screens/realism/`. `screen_designs.py compare/judge` and `tools/check_screenshots.sh` guard drift.
- **Storage convention:** see [ADR-0014](docs/adr/0014-runtime-image-root.md). Never overwrite an existing image; new outputs get a new path or `-vN` suffix.
A guest may have:
The path from the current vertical slice to v1.0.0 — content depth, onboarding, accessibility, platform QA and release readiness — is tracked in [`TODO.md`](TODO.md); this plan stays at the product/design level.
- A valid ID and ticket
- A valid ID but no ticket or reservation
- An expired, damaged, or questionable ID
- An ID that does not match the guest
- A prohibited item detected by the wand
- A harmless item that causes a false alarm
- No ID when identification is required
The player must make decisions based on the venue’s rules and observable evidence, not on a guest’s appearance or attitude. A calm guest with a valid ID should be admitted efficiently, while an aggressive guest with valid credentials may still require assistance if they threaten staff or other patrons.
### Joke Shop Escalation
1. **Nonverbal direction:** Raise a hand, point to the ID-check area, or gesture for the guest to wait.
2. **Routine explanation:** Explain the ID, ticket, or wand-check requirement.
3. **Repeat request:** Clearly state what is missing or what must happen before entry.
4. **Refusal of entry:** Keep the guest outside and direct them away from the entrance.
5. **Manager or security assistance:** Call for a supervisor or another staff member when a guest argues, refuses screening, or blocks the line.
6. **Emergency response:** Call emergency services only for an immediate threat, such as a weapon, assault, or serious attempt to force entry.
The handheld wand should not automatically identify an item as a weapon. The player must interpret the alert, ask the guest to identify the item, and follow venue procedure. The goal is to create a believable screening challenge while keeping the emphasis on communication and proportionate responses.
### Scenario Events
- A busy weekend comedy show creates a long line.
- A performer arrives through the guest entrance and must be recognized or verified.
- A guest forgets their ID but appears on the reservation list.
- A fake or borrowed ID is presented.
- A wand alert comes from keys, a belt buckle, or a phone.
- A guest refuses the screening but insists on entering.
- A disruptive guest attempts to follow another person through the door.
- A late-arriving audience member tries to enter during a performance.
## Hollywood Boulevard Late Night TV Studio
This scenario takes place outside a late-night television studio on Hollywood Boulevard. The guard manages the front steps, studio entrance, guest arrivals, deliveries, audience members, and the public crowd gathered around the building.
The studio encounters all of the NPC types described in the game plan:
- **Tourists** stopping to take photos or watch arrivals
- **Street performers** occupying the steps or performing near the entrance
- **Influencers and filmmakers** recording the guard, guests, or celebrity arrivals
- **Confused visitors** looking for the wrong entrance or trying to attend a show without understanding the access rules
- **Intoxicated pedestrians** approaching the entrance or disrupting the crowd
- **Trespassers** testing the doors, trying to follow authorized guests, or returning after being directed away
- **Violent threats**, which are rare but require careful recognition and emergency escalation
The studio also has authorized guests and staff. They must either be recognized by the guard or present a valid wristband, credential, guest-list confirmation, or other approved access marker. The player must check these credentials without holding up the audience line or allowing unauthorized people to enter.
### Studio Responsibilities
- Keep the front steps and entrance clear.
- Direct tourists and performers away from restricted areas.
- Check wristbands, credentials, tickets, or guest-list status.
- Admit authorized staff, performers, guests, and audience members.
- Manage influencers and filmmakers without unnecessarily obstructing lawful filming.
- Protect celebrity arrivals and production equipment from crowding.
- Coordinate with studio staff, production assistants, and additional security guards.
- Watch for attempts to follow authorized people through the entrance.
- Call for emergency assistance only when there is an immediate threat.
### Studio Scenario Events
- A crowd gathers to watch a celebrity arrive.
- Tourists stop on the steps and block the entrance for photographs.
- A street performer sets up in front of the audience queue.
- An influencer repeatedly ignores the restricted filming boundary.
- A confused visitor believes the studio is open to the public.
- An intoxicated pedestrian wanders toward the stage door.
- A trespasser tries to blend into an audience group.
- An authorized guest arrives without their wristband and must be verified another way.
- Someone presents a counterfeit wristband or credential.
- A violent threat appears during a crowded arrival, requiring the player to protect the entrance while calling for assistance.
The scenario should test the player’s ability to distinguish ordinary Hollywood Boulevard activity from a genuine security problem. The best solution is usually a simple gesture, clear direction, or credential check rather than immediate escalation.
Future venues can reuse this structure while changing the rules, equipment, staff roles, and types of access decisions.
Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [ADR-0008](docs/adr/0008-cone-focus-and-head-ring.md), [ADR-0009](docs/adr/0009-radial-action-menu.md), [ADR-0010](docs/adr/0010-procedural-npc-presentation.md), [ADR-0011](docs/adr/0011-aim-free-selection-and-precision-walk.md), [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md), [ADR-0013](docs/adr/0013-blender-authored-npc-characters.md).
## Tech Stack
## Engine: Godot 4
- **Engine:** Godot 4.3 baseline (local dev verified with 4.7.2), GL Compatibility renderer (Web parity); optional Forward+ Desktop HQ build.
- **Language:** GDScript.
- **Architecture:** venue-driven simulation; shared systems plus `Resource`-based scenario data.
- **Navigation:** `NavigationRegion3D` / `NavigationAgent3D`.
- **NPC behavior:** lightweight custom finite state machines driven by `NpcProfile` resources.
- **UI/audio:** Godot `Control` nodes, themes, procedural audio, and JSON-backed localized strings.
- **Persistence:** JSON in `user://` for settings and shift history.
- **Testing:** custom headless runner (`src/test/test_runner.gd`); GUT was evaluated and not adopted. Python tooling tests cover the asset/screenshot pipelines.
- **Source control/CI:** git on Gitea; `.gitea/workflows/build.yml` runs headless checks and builds/publishes releases on tags.
- **Targets:** Windows, Linux, macOS, and Web. Release artifacts include portables, an NSIS installer, an AppImage, a Web zip, and an optional Desktop HQ zip (Forward+ volumetric lighting; Vulkan required).
The game is built with the Godot Engine 4.x editor. Godot was chosen for its free and open-source license, built-in 3D rendering, navigation, animation, audio, and UI systems, and its low weight for fast prototyping and iteration.
## Versioning and Delivery
## Language: GDScript
Gameplay logic is written in GDScript. It is tightly integrated with the engine, fast to iterate on, and avoids an external build toolchain for the prototype phase.
## Architecture Mapping
The venue-driven architecture maps onto Godot as follows:
- **Nodes and scenes:** Each major system (`ShiftCoordinator`, `NPCController`, `InteractionSystem`, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`, `ShiftReview`) is a Godot node or scene.
- **Scenarios as resources:** `VenueScenario` and content such as `NpcProfile`, `Dialogue`, `EventPool`, and `AccessPolicy` are defined as custom Godot `Resource` types so venue content is data-driven and inspectable in the editor.
- **Navigation:** `NavigationRegion3D` and `NavigationAgent3D` drive NPC and player movement.
- **NPC behavior:** Lightweight custom finite state machines built on the `NPCController` node, with behavior traits coming from `NpcProfile` resources.
- **UI:** Godot `Control` nodes and themes for prompts, access inspection, radio reports, dialogue, timers, and the shift review screen.
- **Audio:** Godot audio buses and `AudioStreamPlayer` nodes for voices, crowd noise, radio, and environment sound.
## Content and Data
- Scenario and entity configuration uses typed Godot `Resource` files (`.tres`), created either in the editor or via scripts.
- Localized dialogue and text are stored in JSON files when they need to be editable outside the editor.
- Save data and shift results use JSON in Godot's `user://` directory.
## Testing
- Unit and scenario tests use the GUT (Godot Unit Test) framework.
- Tests run in headless mode so rules, access verification, escalation, and scoring can be verified without rendering or user input.
## Version Control and CI
- Source is tracked with git.
- GitHub Actions runs headless Godot tests and, in later stages, exports desktop builds.
## Planned Commands
The following commands apply once the Godot project is scaffolded:
- Open the editor: `godot --editor --path .`
- Run the game: `godot --path .`
- Run tests headlessly via GUT.
## Export Targets
Desktop is the first target: Windows, Linux, and macOS. Web export is a later option.
`config/version` in `project.godot` is the single version source (currently `0.9.0`). Releases are tagged `v<version>`; the CI pipeline builds and publishes the standard artifacts plus the optional Desktop HQ zip. Version bumps follow [Semantic Versioning](https://semver.org/). See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands.
## Design Direction
The game should feel like a tense workplace simulation with occasional crisis moments. The most satisfying outcome is not overpowering an NPC; it is recognizing the situation early and resolving it calmly.
A useful guiding rule:
> **The player wins by choosing the lowest level of escalation that keeps everyone safe.**
## Future Ideas
- A rare Bob Dobbs cameo (Easter-egg appearance; placement note in `MEMORY.md`).
- Specific unusual NPC behaviors, such as slapping their own face, using hands as scissors, or interacting with props unexpectedly.
- Live 3D body-camera replay; additional venues or seasonal events; real voice/dialogue audio; an image-to-mesh prop pipeline.
+76 -184
View File
@@ -1,6 +1,6 @@
# Step Line
# Gig Line
Step 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.
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:
@@ -13,224 +13,116 @@ The central design goal is simple:
| ![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) | ![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) from the current build — day and late-night shifts.*
*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
| Platform | Package |
| --- | --- |
| **Windows — installer** | [`step-line-setup-<ver>.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · contains all game data, shortcuts + uninstaller |
| **Windows — portable** | [`step-line-windows-<ver>.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, no install needed |
| **Linux — AppImage** | [`step-line-<ver>-x86_64.AppImage`](https://git.allucanget.biz/zwitschi/security-game/releases) · single file, `chmod +x` and run |
| **Linux — portable** | [`step-line-linux-<ver>.x86_64`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, `chmod +x` and run |
| **macOS** | [`step-line-macos-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unsigned build, quarantine note below |
| **Windows — installer** | [`gig-line-setup-<ver>.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · contains all game data, shortcuts + uninstaller |
| **Windows — portable** | [`gig-line-windows-<ver>.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, no install needed |
| **Linux — AppImage** | [`gig-line-<ver>-x86_64.AppImage`](https://git.allucanget.biz/zwitschi/security-game/releases) · single file, `chmod +x` and run |
| **Linux — portable** | [`gig-line-linux-<ver>.x86_64`](https://git.allucanget.biz/zwitschi/security-game/releases) · 64-bit, `chmod +x` and run |
| **macOS** | [`gig-line-macos-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unsigned build, quarantine note below |
| **Web (browser)** | [`gig-line-web-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unzip and serve the folder from any static host (no special headers) |
| **Desktop HQ** | [`gig-line-desktop-hq-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · optional Forward+ build with volumetric fog/SDFGI/flashlight; requires a Vulkan-capable GPU/driver |
**All releases:** <https://git.allucanget.biz/zwitschi/security-game/releases>
> **Latest: v0.2.0** (2026-10-31) — built and published by Gitea Actions from the
> `v0.2.0` tag. Releases are built automatically when a `vX.Y.Z` tag is pushed
> Downloads are public (no sign-in required) since the host's instance-wide
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
>
> **Latest: v0.9.0** — production-quality audio/accessibility settings, a
> 24-NPC performance baseline and the v0.8 tutorial/campaign/resume pass.
> 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 step-line-macos.zip`. Build instructions and
> `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.
[Setup and Run](#setup-and-run) 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.
The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4 + 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 in `assets/textures/` (≤1024px, power-of-two) produced by `tools/generate_textures.py` and imported by Godot; structural geometry stays procedural (`WorldBuilder`). The same script can generate textures through the OpenRouter image API (`--ai`, requires `OPENROUTER_API_KEY`).
- **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
### 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
```
The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor.
### Controls
| Input | Action |
| --- | --- |
| W/A/S/D + mouse | Move and look |
| Esc | Release / capture mouse |
| Q | Polite direction (Level 1) |
| 1 / 2 / 3 | Simple gestures: raise hand, point, head shake (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 |
| W | Handheld wand screening (wand venues only) |
| A / D | Allow / deny entry during inspection or wand check |
| V | Cycle venue: Hollywood Blvd → The Joke Shop → TV Studio (restarts shift) |
| Tab | Cycle target among nearby NPCs (aim-free alternative) |
| G | Toggle flashlight (widens NPC perception at night) |
| L | Lock / unlock the restricted door (thwarts forced entry) |
| F | Toggle debug overlay |
A typical shift lasts 10–15 minutes: watch the entrance and sidewalk, judge why
each person is approaching, verify access with recognition, wristbands, IDs,
tickets or credentials, and escalate only when necessary. The game rewards calm
resolution over force. The full mechanics — the three venues, the six-level
escalation model, and the core systems — are in
[`docs/gameplay.md`](docs/gameplay.md); the design rationale lives in
[`PLAN.md`](PLAN.md).
## 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.
A venue-driven simulation: shared systems (movement, perception, interaction,
access verification, escalation, radio, crowd, reputation, evidence, shift
review) driven by per-venue scenario data, on Godot 4 + GDScript. The full
arc42-style document is [`docs/architecture.md`](docs/architecture.md);
decisions are recorded as ADRs in [`docs/adr/`](docs/adr/).
## Tech Stack
- **Engine:** Godot 4.3 baseline (local dev verified with 4.7.2) · **Language:** GDScript
- **NPCs:** Blender-authored skinned library (`assets/models/blender/`) with procedural fallbacks; lightweight FSMs on `NpcProfile`
- **World:** procedural `WorldBuilder` + generated prop models
- **Art:** v2 skyline/surface textures in `assets/textures/ai/game_ready/v2/picks/`; UI/scene art in `assets/images/`
- **Asset tooling:** `asset_pipeline.py`, `model_pipeline.py`, `npc_pipeline.py`, `blender_assets.py`, `scene_concepts.py`, `screen_designs.py`, `check_screenshots.sh`
- **Testing:** custom headless runner (`src/test/test_runner.gd`)
- **Targets:** Windows, Linux, macOS, Web
The asset pipelines, model tools, and screenshot gates are documented in
[`docs/asset-tooling.md`](docs/asset-tooling.md).
## Setup and Run
```bash
python3 -m pip install -r requirements-tools.txt
godot --editor --path . # open in the editor
godot --path . # run the game
godot --headless --path . --script res://src/test/test_runner.gd # run all tests
```
Full setup, every run/test/smoke command, and the controls table are in
[`docs/getting-started.md`](docs/getting-started.md).
## 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.
- [`CHANGELOG.md`](CHANGELOG.md) — User-facing changes by version.
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
- [`docs/gameplay.md`](docs/gameplay.md) — Gameplay systems, venues, and the escalation model.
- [`docs/tutorial.md`](docs/tutorial.md) — First-shift tutorial beats and rules.
- [`docs/asset-tooling.md`](docs/asset-tooling.md) — Asset pipelines, model tools, and screenshot gates.
- [`docs/getting-started.md`](docs/getting-started.md) — Setup, run/test commands, and controls.
- [`docs/development-status.md`](docs/development-status.md) — Implemented features and what's next.
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md) — Manual smoke-test checklist.
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
- [`docs/adr/`](docs/adr/) — Architecture Decision Records.
- [`TODO.md`](TODO.md) — Active tasks plus the v1.0.0 gap analysis and milestone roadmap.
## 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: **52 passing** (access rules, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, navmesh, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings)
- 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
- 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)
Not yet implemented: body-camera replay timeline, venue selection startup menu, editor-based GUT integration, and crowd performance profiling/LOD (see `TODO.md`).
### Smoke Test Checklist
1. `godot --path .` starts the game without script errors.
2. The guard moves and looks around; the building, steps, and sidewalk are visible.
3. NPCs spawn and approach the entrance during the first minutes.
4. Aiming at an NPC shows its archetype and intent in the HUD.
5. Pressing `Q` produces a reaction log entry and reputation feedback.
6. `E` opens access inspection showing artifacts and policy result; `A`/`D` record a decision.
7. `6` opens the assistance form; submitting shows dispatch feedback.
8. `R` opens the emergency form; the button must be held ~1.6s or the call cancels.
9. `G` toggles the flashlight (indicator appears in the HUD); `L` toggles the door lock (indicator turns red when locked).
10. `F` toggles the debug overlay with NPC states and shift stats.
11. NPCs route around the building using the navigation mesh (no stuck-at-wall behavior during a smoke run).
12. When the shift timer reaches 0, the shift review screen shows grade, incident summary, and persisted shift history.
v0.8.0 adds the first-shift tutorial, campaign shell, checkpoint resume,
difficulty presets and an in-game Help overlay on top of the v0.7.x venue, gear,
judge and Desktop HQ work. See [`docs/development-status.md`](docs/development-status.md)
for the implemented list and [`TODO.md`](TODO.md) for the v1.0.0 roadmap. Manual
verification uses the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
## Development Workflow
+101 -54
View File
@@ -1,74 +1,121 @@
# TODO
## 🎨 AI texture generation (OpenRouter)
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.
- [x] Extend `tools/generate_textures.py` with an OpenRouter image-API mode
(`--ai`, `--prompt`, `--name`, `--model`, `--size`), decoding base64 PNG
responses and clamping output to ≤1024px.
- [x] Add prompts for all 15 game textures (world surfaces, awning, skyline,
vignette, 4 HUD icons, 5 access cards) and generate them under `assets/textures/ai/`.
- [ ] Make the `ai/` icons/vignette/skyline truly transparent: seedream-4.5 ignores
`background: transparent` (returns opaque images). Either switch to a
transparency-capable model or post-process (e.g. chroma-key the flat background).
## Gap Analysis and v1.0.0 Roadmap
## 🪟 Windows installer (self-contained, all game data)
Baseline: **v0.8.1** (2026-11-01). Three playable venues, tutorial/campaign/checkpoint resume,
difficulty/help, a complete escalation/access/radio loop, shift review, the Blender asset library,
249 headless + 94 Python tests, a clean screen-design judge and an optional Forward+ Desktop HQ
build. This is a strong first-hour vertical slice, not yet a 1.0 product.
Goal: a real Windows installer that includes the entire game (executable + data),
Start-Menu/desktop shortcuts, and an uninstaller — instead of a bare .exe + .pck.
### Documentation audit (completed 2026-11-01)
- [x] Switch the Windows/Linux/macOS export presets to `binary_format/embed_pck=true` so a
single self-contained binary replaces the exe+pck pair (all game data in one file).
- [x] Add a game icon: `assets/icon.png` (AppImage/desktop) + `assets/icon.ico` and
`packaging/windows/step-line.ico` generated by `tools/generate_textures.py`.
- [x] Author `packaging/windows/step-line.nsi` (NSIS): installer that installs the exe, writes
the uninstaller, shortcuts, and registry uninstall entry.
- [x] Add CI packaging steps to `.gitea/workflows/build.yml` (NSIS via apt; AppImage via
appimagetool) so tags/dispatch produce `step-line-setup-<ver>.exe` and
`step-line-<ver>-x86_64.AppImage` next to the portable binaries.
- [x] Extend `tools/publish_release.sh` artifact glob to upload the installer/AppImage too.
- [ ] Verify on CI: NSIS installer builds (cannot be run locally — no `makensis` on this box).
- [ ] Verify the Windows installer contents in CI (e.g. `7z l` shows the embedded data file).
- [x] Make `docs/architecture.md` the single architecture source: asset/content pipeline
contracts, runtime venue-switch/fallback flows, deployment/version constraints, quality
requirements, risk register and documentation ownership map.
- [x] Keep command-heavy operational pages separate but subordinate: `docs/asset-tooling.md`,
`docs/blender-assets.md`, `docs/getting-started.md`, `docs/RELEASING.md` and
`docs/smoke-checklist.md` link to architecture rather than acting as parallel sources.
- [x] Remove the obsolete `docs/plan-graphics-v0.6.md` and its links; completed graphics work is
represented by ADR-0013, the asset pipeline section and the changelog.
- [x] Resolve stale claims: README release version/download row, development status
"Forward+ not implemented", and Blender props "not AO-baked".
- [x] Record the documentation ownership rule in `docs/architecture.md` §12: design intent
(`PLAN.md`), mechanics (`gameplay.md`), system architecture/contracts (`architecture.md`),
decisions (ADRs), status (`development-status.md`), roadmap (`TODO.md`), operations
(tooling/release docs).
## 🐧 Linux AppImage (self-contained, all game data)
### Gap register
Goal: a single executable `.AppImage` that runs the full game on any recent distro.
| Area | Current baseline | Gap to 1.0 | Risk | Milestone |
| --- | --- | --- | --- | --- |
| First-hour experience | Venue menu and controls exist; no tutorial | No guided first shift, control coaching, objective recap or "what went wrong" debrief | New players bounce before the de-escalation loop lands | 0.8.0 |
| Campaign/progression | Venue selection and debug cycle | No career/venue order, unlocks, difficulty curve or persistent player progression | Shift loop feels like a tech demo rather than a game | 0.8.0 |
| Authored content | 3 venues, small event pools, one-line dialogue | Need a larger event matrix per venue, repeat/remix rules, failure/recovery events and more NPC dialogue | Repetition after a few shifts | 0.8.0 |
| NPC behavior | FSM intents, reactions, crowd/theater poses | Deeper negotiation, memory of earlier interactions, group dynamics, believable refusal/escalation edges | NPCs still feel deterministic/scripted | 0.8.0 |
| Access/evaluation | Recognition, wristbands/IDs/tickets/credentials, reputation/review | Fairness tuning, ambiguous cases, clearer evidence trail, richer grades and actionable feedback | Players cannot learn why a decision scored poorly | 0.8.0 |
| Shift lifecycle | Settings + completed-shift history persist | No mid-shift save/resume, pause-safe serialization, versioned save schema or replay | Interrupted sessions lose the run; future changes can break saves | 0.8.0 |
| Accessibility | Key remapping, gamepad, HUD font scale, shape escalation cue | Subtitles/captions, colour-independent cues, motion/audio sensitivity, text expansion, menu narration and full input alternatives need an audit | Public 1.0 cannot claim accessible play | 0.8.0–0.9.0 |
| Localization | English strings in `content/strings.en.json` | String extraction/coverage, pseudo-locale test, font fallback, text-length layout budgets and translator workflow | Text bugs and unsupported locales at release | 0.9.0 |
| Audio | Procedural crowd bed and radio crackle | Music/ambience direction, event SFX, voice/VO decision, caption hooks and mix budgets | Scene can feel silent/placeholder | 0.9.0 |
| Animation/presentation | Procedural poses, walk/idle theater, LOD impostor | More reaction/idle/negotiation poses, transitions, facial readability at portrait distance, camera-height decision | Cinematic close-ups expose the prototype layer | 0.9.0 |
| Performance/platform | 16-NPC budget measured headlessly; Compatibility default; optional Vulkan HQ | Target hardware matrix, 24-NPC profiling, draw-call/memory budgets, Web/HQ parity and frame-time regression gates | Crowded studio or Web drops frames | 0.9.0 |
| Content tooling | ScenarioFactory/.tres export, Blender manifest, asset audits | Schema validation, previews, event authoring workflow, reproducible asset rebuild notes and contributor onboarding | Content expansion becomes expensive/error-prone | 0.8.0–0.9.0 |
| QA/CI | Headless suite, smokes, screenshot compare, model-aware judge | End-to-end shift automation, full venue matrix, CI Godot-version strategy, manual judge budget and release checklist automation | Regressions escape or CI becomes non-reproducible | 0.9.0 |
| Release/legal | Tag-driven unsigned builds, Web zip, optional HQ | Signing/notarization, checksums, AI-asset provenance/licensing, credits, privacy/body-camera statement and storefront requirements | Public release blocked by trust/policy issues | 0.9.5 |
| Documentation | Layered docs consolidated; drift was found | Keep changelog/status/roadmap/ADR links synchronized per release; add contributor architecture tour | Docs become stale again | Continuous |
- [x] Author AppDir files: `packaging/linux/step-line.desktop`, `packaging/linux/AppRun`,
icon wiring.
- [x] Add `packaging/linux/build_appimage.sh` (appimagetool; `APPIMAGE_EXTRACT_AND_RUN=1` to
avoid FUSE dependency) producing `step-line-<ver>-x86_64.AppImage`.
- [x] Local verification: built the AppImage on this machine and ran it headless
(`--appimage-extract-and-run --headless --quit-after 90` → 0 script errors,
`[Step Line] game started`).
- [x] AppStream metainfo added (`packaging/linux/step-line.appdata.xml`); tool validates clean.
- [ ] CI verification: AppImage step runs on tags/dispatch and the artifact uploads.
- [x] README Downloads table documents installer + AppImage rows and naming.
## Milestone 0.9.5 — Release candidate
## Distribution follow-up (host config, no code work)
Goal: freeze content, prove it with external players, clear release/legal blockers and verify
every distribution path.
- [ ] Asset downloads currently require sign-in on the host (`/releases/download/…` redirects to
login). Decide with the host admin: open repo visibility / `REQUIRE_SIGNIN_VIEW=false` or
accept sign-in-required downloads. Pages and the tag page are already public (HTTP 200).
- [ ] Content lock and external playtest round (at least five first-time players); triage findings
by severity and fix all P0/P1 issues.
- [ ] Balance and fairness pass across all venues: scoring, difficulty, event weights, NPC
compliance and emergency false-positive/negative rates.
- [ ] Save migration test from 0.8/0.9 saves; verify settings, shift history and resume paths.
- [ ] Release engineering: signed/notarized macOS where possible, checksums, Windows/Linux install
smoke tests, AppImage update, Web hosting verification and Desktop HQ Vulkan fallback.
- [ ] Legal/credits review: AI-assisted asset provenance, music/SFX licenses, privacy/body-camera
language, age/content rating and storefront requirements.
- [ ] Documentation sign-off: architecture/ADR/status/TODO/changelog/README/smoke checklist all
match the release candidate; capture final gallery and run compare + judge.
- [ ] Exit: release candidate passes the manual smoke checklist on every target; no release-blocking
legal or platform issue remains.
## Future Systems (design-level backlog)
## Milestone 1.0.0 — Launch
- [ ] Body-camera replay timeline in the shift review.
- [ ] Venue selection startup menu (keyboard cycling with `V` is implemented).
- [ ] IDE/editor-based GUT integration when a Godot editor workflow is introduced.
Goal: publish a stable, documented and supportable 1.0 build.
## Open Polish
- [ ] Freeze content and version, run full final QA on Windows/Linux/macOS/Web/Desktop HQ, and
verify fresh installs plus upgrade paths.
- [ ] Publish store/release notes, credits, known limitations, accessibility statement and
controls; verify every download link and artifact checksum.
- [ ] Tag `v1.0.0`, publish through CI, verify the release page and post-launch patch policy.
- [ ] Definition of 1.0: three complete venues with campaign/tutorial, stable save/resume,
accessibility/localization acceptance, measured performance on the target matrix, signed
release artifacts where platform rules require it, and documentation synchronized with the
build.
- [ ] Keep `%` placeholders stable in `Strings.fmt` when adding keys (positional formatting).
## Post-1.0 / non-blocking candidates
## Detailed Graphics — remaining performance items
- [ ] Live 3D body-camera replay.
- [ ] Additional venues or seasonal events.
- [ ] Real voice/dialogue audio.
- [ ] Image-to-mesh prop pipeline.
- [ ] Rare easter eggs/cameos (e.g. Bob Dobbs) only after core content and QA are stable.
The visual pass is done (textures, rigs, HUD feedback — see `DONE.md`). Remaining work is
performance hardening only.
## Roadmap dependencies and risks
- [ ] Profile the crowd: ensure textured materials and props hold target frame rate around a
busy entrance.
- [ ] Add LOD or culling for distant NPCs/props if the crowd grows.
- Content volume is the largest schedule risk; tutorial and campaign work must not outrun the
event/dialogue matrix.
- The vision judge is a dev-time aid with an external key/budget; CI must never make it a hard gate.
- Godot version changes can alter lighting/imports; do not combine an engine baseline move with a
content/feature milestone.
- Signed macOS builds and legal/AI provenance are release blockers, not polish.
- Keep the de-escalation-first rules and rare-emergency design unchanged while adding progression,
difficulty and feedback.
## Screenshot Refresh
## Realism follow-ups (deferred)
- [ ] Regenerate and verify the six README screenshots from the current build.
- [x] Re-run the screen-design judge for `12-joke-shop`, `13-studio-arrival` and the
Forward+ A/B with the valid key: **13/13 match, 0 diff**; the HQ A/B is in the report.
- [ ] 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,
which is still flat-shaded clumps (`_shrub()`/`_tree()` in `tools/blender_assets.py`).
Only worth doing if a higher-poly organic builder is added to the library.
- [ ] Camera height: the concept frames are shot at ~1.55 m with a slightly wider lens; the game
camera sits at 1.7 m. Decide whether the first-person height should match the art direction
or stay at the current gameplay value.
## Future ideas (optional, not scheduled)
- [ ] Live 3D body-camera replay (the timeline is text-only today).
- [ ] Additional venues or seasonal events authored from the new `.tres` files.
- [ ] Real voice/dialogue audio (the crowd bed and radio crackle are procedural).
- [ ] Swap the primitive prop builder for an image-to-mesh model API behind the same
`assets/models/generated/<prop>.glb` contract.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.
Loaded 100 of 402 files, more files were not shown because too many files have changed in this diff. Show more