Compare commits

...
41 Commits
Author SHA1 Message Date
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
296 changed files with 13150 additions and 501 deletions

No files matched your search

+8 -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,7 +106,7 @@ 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
+30 -1
View File
@@ -43,4 +43,33 @@ user://
.gitea/logs/
build/
.env
.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): the candidate pool and per-run captures
# stay local; the validated picks are tracked under game_ready/v2/picks/ (ADR-0014),
# and each run commits its report.{json,md} + sheet.jpg. Concept references are
# committed in docs/screens/concepts/.
assets/textures/ai/game_ready/v2/*
!assets/textures/ai/game_ready/v2/picks/
docs/screens/realism/**/*.png
+5 -1
View File
@@ -39,6 +39,10 @@ All planned implementation work must be represented in `TODO.md`.
- Use checkbox items for each concrete task.
- 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.
@@ -52,7 +56,7 @@ Recommended format:
## Core Gameplay
- [ ] Add player movement controls.
- [ ] Implement NPC approach behavior.
- [-] Implement NPC approach behavior. (claimed; implementation/verification in progress)
- [x] Define escalation levels.
## Documentation
+448 -12
View File
@@ -163,7 +163,7 @@ Completed task sections moved from `TODO.md`. Sections are moved only when every
applied on phase change and shift start.
- [x] Street lamps: poles with emissive bulbs + OmniLights on the sidewalk;
`WorldBuilder.set_night_mode` switches bulbs/lights on at night.
- [x] Marquee signage: emissive “STEP LINE” strips + Label3D above the entrance.
- [x] Marquee signage: emissive “GIG LINE” strips + Label3D above the entrance.
- [x] Street dressing: crosswalk stripes, planters, and low-poly trees.
- [x] NPC visual variety: per-seed height/brightness variation (deterministic LCG).
- [x] Denser routine traffic (tourists/performers/influencers/trespassers).
@@ -211,21 +211,21 @@ Completed task sections moved from `TODO.md`. Sections are moved only when every
- [x] Export presets: `binary_format/embed_pck=true` on all platforms — every binary is now
self-contained (all game data in one file; `.pck` no longer shipped).
- [x] App icon: `assets/textures/icon.png` (1024) + multi-size `icon.ico`, copied to
`packaging/windows/step-line.ico` and `packaging/linux/step-line.png` via the generator.
- [x] NSIS script `packaging/windows/step-line.nsi` (installs `Step Line.exe`, uninstaller,
`packaging/windows/gig-line.ico` and `packaging/linux/gig-line.png` via the generator.
- [x] NSIS script `packaging/windows/gig-line.nsi` (installs `Gig Line.exe`, uninstaller,
shortcuts, registry entry; `makensis /DVERSION=… /DARTIFACT=…`).
- [x] AppImage: `packaging/linux/step-line.desktop`, `AppRun`, appdata.xml, and
- [x] AppImage: `packaging/linux/gig-line.desktop`, `AppRun`, appdata.xml, and
`build_appimage.sh` (appimagetool, `APPIMAGE_EXTRACT_AND_RUN=1`).
- [x] Local end-to-end: rebuilt embedded exports and produced `step-line-0.2.0-x86_64.AppImage`,
- [x] Local end-to-end: rebuilt embedded exports and produced `gig-line-0.2.0-x86_64.AppImage`,
ran it headless (0 script errors).
- [x] CI: NSIS + AppImage steps added to `.gitea/workflows/build.yml` build job; publish glob
already uploads `build/step-line-*<ver>*` (covers setup/AppImage names).
- [x] CI run 1110 verified end-to-end (v0.2.1): NSIS installer `step-line-setup-0.2.1.exe`
built, `7z l` shows `step-line.exe` + `uninstall.exe`, AppImage built (33.9 MB),
already uploads `build/gig-line-*<ver>*` (covers setup/AppImage names).
- [x] CI run 1110 verified end-to-end (v0.2.1): NSIS installer `gig-line-setup-0.2.1.exe`
built, `7z l` shows `gig-line.exe` + `uninstall.exe`, AppImage built (33.9 MB),
release `v0.2.1` published, `Job succeeded`.
- Gotchas fixed on the way: makensis needs `-D` defines (not `/D`) and absolute paths
(it resolves relatives against the script dir); installed exe must be space-free
(`step-line.exe`); icon.ico must use classic BMP entries (ImageMagick; PIL emits
(`gig-line.exe`); icon.ico must use classic BMP entries (ImageMagick; PIL emits
PNG-compressed ICOs NSIS rejects).
## 🎮 Assets & detailed graphics pass (textures, rigs, UI feedback)
@@ -234,7 +234,7 @@ Completed task sections moved from `TODO.md`. Sections are moved only when every
seamless concrete, asphalt, awning, skyline, vignette, HUD icons, access-card art — all
≤1024px and power-of-two; imported under `assets/textures/`.
- [x] Environment: textured facade + glass door, concrete sidewalk, asphalt street, striped
awning with “STEP LINE”, generated skyline backdrop planes, soft sun shadows.
awning with “GIG LINE”, generated skyline backdrop planes, soft sun shadows.
- [x] NPCs: humanoid rig (torso/head/limbs) with archetype props (camera, delivery box, hat,
sign, backpack), procedural walk bob, head-turns-toward-guard cue.
- [x] UI/feedback: inspection artifact cards, mode icons (flashlight/door/wand), escalation
@@ -412,7 +412,7 @@ Completed 2026-10-31 (working tree). Headless **88/88**; `src/test/smoke_session
offset and the spawn `y`, and verify eye height while walking the steps.
- [x] Steps and the landing are visual-only (no collider). The guard walks through them; add
simple step colliders or restage the landing so it does not intersect the walk path.
- [x] Marquee text (`STEP LINE`) is tiny from the sidewalk. Scale the Label3D up.
- [x] Marquee text (`GIG LINE`) is tiny from the sidewalk. Scale the Label3D up.
- [x] Focus circle and name tag are small at 2–3 m. Consider a larger circle / font after a
real playtest.
- [x] NPC crowd cap (`MAX_LIVE_NPCS`) silently drops routine traffic at 16. Add a short
@@ -655,7 +655,7 @@ produces a 63 MB static bundle and it serves locally with HTTP 200s for html/was
- [x] Add a `Web` preset to `export_presets.cfg` using `variant/thread_support=false` so the
build runs on plain static hosting without COOP/COEP headers.
- [x] Export to `build/web/` and package `build/step-line-web-<version>.zip` from
- [x] Export to `build/web/` and package `build/gig-line-web-<version>.zip` from
`tools/build_releases.sh` (zip or Python fallback).
- [x] Publish the web zip with the desktop artifacts (`tools/publish_release.sh`).
- [x] Cover the preset, the non-threaded choice, and the release wiring in
@@ -691,3 +691,439 @@ tooling **53/53**.
`OPENROUTER_API_KEY` by hand.
- [x] Verified every reference is 1280x720, reviewed the new images visually, and confirmed
`list` reports 19 designs (12 kept, 7 new).
## Staged Captures For The New Screens
Completed 2026-11-01 (v0.3.3). `screen_designs.py compare` reports **9 good / 0 bad**;
vision judge: wand **match** (0.90), timeline **match** (1.00), escalation **review**, 0 diff.
Python tooling **53/53**, headless **182/182**.
- [x] `capture_screenshots.gd` now continues past the night set: switches to The Joke Shop,
stages a guest, retries the wand until it alerts (falls back to the documented keys
false alarm), applies formal warning + blocking, and opens the body-camera timeline.
- [x] New captures `07-wand-screening.png`, `08-escalation-hud.png`, `09-body-camera-timeline.png`
added to `docs/screenshots/` and to the README gallery.
- [x] Wired `ref` for `wand_screening`, `escalation_hud` and `body_camera_timeline` so
`compare` and `judge` include them (9 of 19 screens now have a staged capture).
- [x] Regenerated the escalation HUD mockup as a third-person over-the-shoulder night shot so
the reference matches the game camera.
- [x] Added `Hud.clear_log()` so staged captures drop target-cycling noise; the escalation shot
keeps the real reaction lines.
## Prop Collision And Placement
Completed 2026-11-01 (v0.4.0). Headless **187/187**, Python tooling **53/53**, the new
`smoke_props.gd` passes windowed, and `screen_designs.py compare` still reports 9 good / 0 bad.
- [x] Every generated prop now gets a fitted static collider: a cylinder for bollard, bin, and
hydrant, a box for everything else. The collider is computed from the model's mesh bounds in
holder-local space, so placement rotation and variant scale carry over.
- [x] Added `smoke_props.gd` (windowed): the guard walks into a bench and stops instead of passing
through it; a guest heading for a point beyond the same bench routes around it with
navigation avoidance and never ends up inside the collider.
- [x] Props publish a `NavigationObstacle3D` footprint, and `NpcAvatar` enables agent avoidance with
a `velocity_computed` callback. The navmesh stays a flat walkable plane; avoidance does the
steering around street furniture.
- [x] Replaced the hard-coded prop calls with `WorldBuilder.PROP_PLACEMENTS`, grouped 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.
- [x] Deterministic variants per placement (`variant_for`): yaw jitter, subtle scale, and optional
procedural clutter (bag by the bin, crate by the newspaper boxes, cone by the hydrant), so
repeated props stop looking cloned.
- [x] Fallback primitive props go through the same bounds fit, so a missing `.glb` cannot silently
remove collision; a unit test covers the fallback body, shape, width, and navigation obstacle.
- [x] Tests: placement validation and its failure modes, variant determinism/range, colliders on
every placed prop (including obstacle presence), and the fallback collider path.
## Prop Pipeline Checks
Completed 2026-11-01. Python tooling **56/56**, headless **188/188**, prop gallery **match**
(0.90) in the vision judge, `compare` **10 good / 0 bad**.
- [x] `audit_glb()` now decodes positions/UVs/indices and enforces the art budget
(`MAX_TRIANGLES = 400`, `MAX_VERTICES = 600`), rejects degenerate triangles, UVs outside
`[0, 1]`, a prop that floats above or sinks below the ground (`min y ~= 0`), and the
single-material/atlas contract. The newspaper box sat 3 cm high; its catalogue parts moved
down so it rests on y = 0.
- [x] Added `capture_prop_gallery.gd`: renders all six props on a studio backdrop (two rows on a
display platform) to `docs/screenshots/10-prop-gallery.png`, with a `prop_gallery`
catalogue entry and AI mockup, so `compare`/`judge` cover close-range prop art.
- [x] Confirmed instances share the imported atlas material (headless test) and documented the
triangle/vertex budget in the pipeline docstring.
- [x] README lists the gallery capture command and image; the pipeline docstring and audit docs
describe the budget checks.
## Better NPC Characters
Completed 2026-11-01 (v0.5.0). Headless suite **208/208**; NPC budget smoke reports 16 avatars,
534 meshes, ~6.9 ms average frame time; PLAN-verb, crowd, emergency, and timeline smokes pass.
- [x] Replaced the rigid capsules with an articulated rig: hip -> thigh -> knee -> shin -> shoe and
shoulder -> upper arm -> elbow -> forearm -> hand, with knee/elbow pivots driven by the walk
cycle and the seated pose.
- [x] Layered clothing (`layer_for`): jacket, hi-vis vest, coat, and dress over the base outfit,
with role-appropriate defaults and deterministic colourways.
- [x] Extended accessories and hand items: bag, umbrella, phone, camera, clipboard, cup, plus the
existing cap/hood/backpack/sunglasses/lanyard. Hand items attach to the right hand socket.
- [x] Face and hair variety: ears, brows, mouth, five hair silhouettes (long, bun, beard, bald,
short), height range, and deterministic per-seed appearance.
- [x] Added idle behaviour layers (`idle_style_for`): breathing, weight shift, look-around, phone,
clipboard, and crossed arms; escalation gestures raise both hands (L3+) or point (L2), and
noticed NPCs turn their head toward the guard.
- [x] Walk cycle now swings hips/shoulders, bends knees and elbows, and uses a faster, longer
stride when fleeing.
- [x] Door queue staging: GameManager sorts waiting guests and assigns spaced `queue_slot_for`
offsets, so one person is at the door and the rest wait behind without overlapping.
- [x] Reaction readability: a billboarded symbol above the head (`!!` panic, `?!` watching, `!`
blocked, `?` warned) updates from the reaction/escalation state.
- [x] Three LOD tiers with hysteresis: full detail, simplified (props/shadows off), and a
billboard impostor; `apply_lod` is tested for the swap.
- [x] Re-measured the live-NPC budget with the full rig (16 avatars, 534 meshes) and added
`smoke_npc_budget.gd`, which fails above the documented 50 ms frame budget.
- [x] Tests: deterministic layers/hand items/hair, role layers, queue slot spacing, LOD
thresholds/hysteresis, reaction labels, articulated pivots, mesh budget, and the
walk-to-idle animation transition.
## Better Player Movement
Completed 2026-11-01 (v0.5.0). Windowed `smoke_movement.gd` passes; headless tests cover surface
lookup, constants, gamepad bindings, and settings persistence.
- [x] Split acceleration and braking (`accel` 22, `decel` 30 m/s²) so stopping is snappier than
starting, and fixed the inverted forward axis found while staging the movement smoke: W now
walks toward the camera's forward vector instead of moonwalking.
- [x] Step-up assist (0.32 m) climbs kerbs and the entrance steps, with wall sliding left to
`move_and_slide`; the smoke walks the guard from the sidewalk onto the landing.
- [x] Camera feel: speed-scaled head bob, a landing dip, and a precision-walk FOV change, all
gated by the new "Head bob and camera motion" setting; a motion-sickness toggle persists.
- [x] Precision walk shows a HUD chip and plays a short tick when it engages, in addition to the
slower speed.
- [x] Surface-aware footsteps (concrete/asphalt/stone) synced to stride distance, plus a landing
cue; both have observable counters for tests.
- [x] Gamepad support: left-stick movement, right-stick look with deadzone, A/B/X/Y face buttons,
LB slow, RB wand; bindings are added at runtime and the control legend swaps to gamepad
hints when a pad event is seen.
- [x] Moved the remaining hard-coded gameplay keys (Q/F/G/L/B/V, Tab cycle) onto named InputMap
actions, and the panel allow/deny keys onto movement actions.
- [x] Key/button remapping in the settings panel: click a binding, press a key or pad button,
persisted as `input_bindings` through `Save`; `InputBindings.apply` rewrites only key/button
events and leaves mouse and gamepad axes alone.
- [x] Added invert-Y and head-bob settings (with save/load round-trip tests) alongside the
existing look sensitivity.
- [x] Tests: surface lookup, step/stride/precision constants, gamepad binding presence and
deadzone, remap descriptor/apply/labels, and settings persistence for camera and bindings.
- [x] Added `smoke_movement.gd` (windowed) and `11-precision-walk.png` with a movement catalogue
entry, AI mockup, and compare coverage.
## NPC Facing During Movement
Completed 2026-11-01 (post-v0.5.0, pre-v0.6.0). Headless suite **209/209**.
- [x] Root cause: `NpcAvatar` used Godot's `look_at()`, which aims the node's **-Z** axis at
the target, but the procedural rig's face (eyes, brows, mouth, focus disc) and every
Blender-authored successor face **+Z**. Every walking NPC therefore showed its face
toward the side it was leaving.
- [x] Added the pure `NpcAvatar.yaw_towards(direction)` helper (+Z forward, tested at +Z/+X/-Z
and an arbitrary direction), and replaced `look_at()` in the navigation and direct-move
paths plus the reaction-face turn.
- [x] Corrected the head-turn offsets to use the same +Z convention (`basis.z` instead of
`-basis.z`), so noticed NPCs still look at the guard with the fixed facing.
- [x] Added `test_yaw_faces_the_movement_direction`; recaptured the targeting screenshot to
confirm NPCs now show their back only when walking away.
## Blender Asset Library Integration
Completed 2026-11-01 (v0.6.0 work). Headless **222/222**, Python **68/68**, all windowed/headless
smokes pass, `compare` **11 good / 0 bad**. AO/ORM baking remains in Phase 4.
- [x] Committed the audited library (8 characters, 18 gear, 20 props), generator, `.blend`
sources, manifest, review sheets, and integration notes.
- [x] `NpcAvatar` selects a character variant by role/seed (`npc_casual`, `npc_jacket`,
`npc_coat`, `npc_vest`, `npc_dress`, `npc_hoodie`, `npc_firefighter`) with `npc_base` and
the procedural rig as fallbacks; the choice is recorded in `character_model`.
- [x] Only the five contract slots (`skin`, `cloth`, `pants`, `accessory`, `hair`) are tinted;
fixed detail materials keep their authored colour. `npc_vest` keeps its fixed hi-vis vest,
and firefighter/guard use dark uniform tints.
- [x] Library gear replaces the procedural accessories and hand items at the documented
`head` / `back` / `chest` / `hand` attach points (cap, hood, helmet, backpack, SCBA tank,
lanyard, phone, cup, clipboard, umbrella, camera, sign, flashlight, wand, radio), with the
primitive path retained as fallback.
- [x] `WorldBuilder` loads the Blender replacements (`bollard`, `bench`, `trash_bin`,
`planter_box`, `newspaper_box`, `fire_hydrant`, `street_lamp`, `street_tree`,
`traffic_cone`) and places the new dressing (`palm_tree`, `round_planter`, `a_frame_sign`,
`crowd_barrier`, `stanchion`, `security_podium`, `handrail`,
`double_door`, `marquee`, `sedan`, `wall_lantern`) with colliders/avoidance and placement
validation.
- [x] Library emissive surfaces (lamp bulbs, marquee bulbs/sign face, wall lanterns, door LED,
sedan lights) are collected and driven by `set_night_mode()`.
- [x] Added a library triangle counter and 60k triangle budget check; the current set is
~27,986 triangles. NPC budget smoke: 16 avatars = 112 meshes at ~6.9 ms.
- [x] Recaptured the README/catalogue screenshots (including the prop gallery from the library
models) and fixed the marquee text placement.
## Environment and Lighting Lift
Completed 2026-11-01 (v0.6.0). Headless **222/222**, Python **69/69**, all smokes pass,
`compare` **11 good / 0 bad**.
- [x] Baked per-asset AO maps for the full Blender library with `tools/bake_ao.py` (Cycles,
256×256, stored next to each GLB as `<name>_ao.png`); Godot wires the red channel into
`StandardMaterial3D.ao_texture` for character slots, gear and props.
- [x] Regenerated brick, concrete and asphalt at 1024² through `asset_pipeline.py`, derived
tangent normal maps with `tools/make_normal_maps.py`, and wired them into
`SurfaceMaterials.textured()` while keeping the existing UV scales.
- [x] Evaluated Forward+: it boots on the desktop Vulkan driver, but the renderer is a
project-wide setting and the Web build must stay on GL Compatibility. Decision: keep
Compatibility for v0.6.0; a desktop-only preset needs a separate project/override and is
deferred.
- [x] Recaptured `docs/screenshots/` and ran `compare` + `judge`; no regressions.
- [x] Fixed the library marquee text/face alignment and added the library AO/triangle tests.
## v0.6.0 Release
Completed 2026-11-01. `config/version` = `0.6.0`; ADR-0013 marked Accepted; CI published six
artifacts (Linux, Windows, macOS, NSIS installer, AppImage, Web) for tag `v0.6.0`.
- [x] Bumped the version and synchronised README/architecture/blender-assets/plan docs.
- [x] Headless **222/222**, Python **69/69**, all smokes, `compare` **11 good / 0 bad**.
- [x] Release verified: `https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.6.0`.
## Runtime Image Path Migration
Completed 2026-11-01. Historical note: the screen-design references originally lived only in
`docs/screens/designs/`; they were promoted to the runtime root `assets/images/ui/` under
ADR-0014. `docs/screens/designs/` remains a reference-only snapshot; `tools/screen_designs.py`
now reads and writes `assets/images/ui/`.
## Realistic Backgrounds, Textures and Scene References
Completed 2026-11-01 (part of the v0.6.x realism pass). The flat box skyline was replaced by
per-phase AI panoramas, the surface tiles by higher-detail v2 picks with normal maps, and the
lighting/exposure was retuned against AI concept frames. Evidence:
`docs/screens/realism/2026-11-01-adopt/` (`report.md`, `sheet.jpg`), `screen_designs.py compare`
**11 good / 0 bad**; headless **227/227**, Python **86/86**, all smokes.
### Realistic backgrounds, textures and scene references
Goal: replace the outdated flat skyline backdrop (`assets/textures/skyline.png`, black boxes
with dot windows) and the flat-looking surface tiles with AI-generated, more realistic
backgrounds and textures. Add a larger set of example 3D scene images as art direction, and
measure the in-game result against that target with the screenshot tools. Plan:
[`PLAN.md` → Visual Quality Direction → Realism pass](PLAN.md#realism-pass).
**Rule for every step: never overwrite an existing image.** New outputs get new paths. When a
path is taken, the tools pick the next free `-vN` suffix. Raw AI output stays on disk under
`assets/textures/ai/v2/raw/` (gitignored like the other raw AI folders). Processed game-ready
candidates go to `assets/textures/ai/game_ready/v2/`, a local pool where only the validated picks
are tracked (ADR-0014). Scene references go to `docs/screens/concepts/`, and trial captures and
reports to `docs/screens/realism/<run>/`.
#### Step 1 — Tooling (`tools/scene_concepts.py`)
- [x] Add a catalogue of prompts in three groups: `scene` (16:9 example 3D game frames),
`backdrop` (4:1 skyline panoramas with transparent sky), and `texture` (seamless PBR
surface tiles). Each entry has a name, prompt, role and target path.
- [x] `generate [--group] [--only] [--variants N]`: call the OpenRouter image API through
`generate_textures.generate_image` (key from the repo `.env`), save raw output, and never
overwrite (`next_free_path`). Print every file written.
- [x] `process`: turn raw backdrops into 2048×512 RGBA strips that tile horizontally (edge
crossfade, sky made fully transparent, hard alpha for the alpha-scissor material). Turn raw
textures into seamless 1024² tiles (`asset_pipeline.make_seamless`) plus a normal map
(`make_normal_maps.normal_map`). Skip outputs that already exist.
- [x] `audit`: run `asset_pipeline.audit_image` on the processed candidates (`backdrop` /
`tile` roles) and fail on any bad result.
- [x] `realism`: for each trial capture, report the Pillow detail metrics (edge density,
contrast, colour spread, saturation) next to the matching concept image. Optionally ask a
vision model (`google/gemini-2.5-flash-lite`) for 0–10 realism scores (lighting,
materials, texture detail, backdrop, overall) with concrete fixes. Write a new JSON +
Markdown report per run.
- [x] Unit tests (`tools/test_scene_concepts.py`): catalogue shape, the no-overwrite path
picker, backdrop/tile processing on synthetic images, metrics, and the judge payload/parse
(offline, no network).
#### Step 2 — Example 3D scene images
- [x] Generate 3D-game-style example frames for every venue and time of day: Hollywood entrance
(morning, golden hour, rainy night), boulevard skyline view (day, dusk), Joke Shop alley
(evening, close-up of the door), studio stage door (night arrival), sidewalk material
close-up, entrance steps and podium, crowd-control barrier line, and a responder arrival
wide shot.
- [x] Review the set and record the visual targets each frame sets (sky, haze, material detail,
backdrop depth) in the report.
#### Step 3 — Backgrounds (replace the outdated skyline)
- [x] Generate skyline panoramas for day, dusk and night (lit windows): Hollywood/Los Angeles
mid-rise blocks, palm silhouettes, the hills on the horizon, a transparent sky, and a
straight-on view, in several variants each.
- [x] Process them into `assets/textures/ai/game_ready/v2/skyline_{day,dusk,night}*.png`,
seamless horizontally for the 3× UV repeat around the backdrop ring. Strips are 2048×1024
(2:1, one UV repeat of the 400×60 m ring is about 133×60 m). 12 candidates, 11 audit
clean; `skyline_day-v3` misses the seam limit by 0.6. Variants 1–2 contain garbled
"Hollywood" lettering, so the prompt now forbids signs. Picks: `skyline_day-v4`,
`skyline_dusk-v3`, `skyline_night-v4`.
#### Step 4 — Textures
- [x] Generate higher-detail seamless tiles: red brick facade, granite/limestone facade
(studio), sidewalk concrete pavers with joints, asphalt with aggregate, wet night asphalt,
stone steps, striped awning canvas, and stucco wall.
- [x] Process them into 1024² game-ready tiles plus normal maps in
`assets/textures/ai/game_ready/v2/` and audit them (16 tiles + 16 normals, all good). The
`awning_canvas` seam crossfade smears the stripes at the tile edge; it needs a
stripe-aligned crop.
#### Step 5 — In-game trial captures (no game-code change)
- [x] Add `src/test/capture_realism.gd`. It boots the Hollywood venue, captures a fixed shot
list (entrance day, boulevard/skyline day, boulevard night), and can swap textures at
runtime from `--swap=OLD=NEW` / `--tint=` / `--normal=` user arguments (the first version used
`--skyline=`/`--brick=`/… flags; `--swap` is path-based and also covers night stages). The
swap finds materials by their original texture path, so baseline and candidate frames use
the same camera. Writes to the `--out=` directory.
- [x] Capture the baseline (current assets) and the candidate set (v2 skyline + textures), and
copy them into `docs/screens/realism/<run>/{baseline,candidate}/`.
#### Step 6 — Compare against the realistic target
- [x] Run `scene_concepts.py realism` on baseline and candidate. Record the metric deltas and the
vision scores, and note which candidate textures/backdrops win. Run
`docs/screens/realism/2026-11-01-v2/` (`report-v2.md`, `sheet.jpg`) gives these mean
judge scores: backdrop 2.6 → 3.6 (v2 day + night skyline) → **4.0** (v2 day + dusk
skyline for the Evening phase), texture detail 1.2 → 1.6–1.75, overall 1.6 → 2.0. Single
scores swing by ±1–2 between runs, so read them as trends. The `characters` score
moved from 3.0 to 1.0 with identical NPCs, which shows the noise.
- [x] Note the remaining realism gaps (sky/fog, lighting, building geometry, NPC fidelity) as
follow-up tasks below.
#### Step 6b — Follow-ups from the 2026-11-01 realism report
- [x] Fix sidewalk overexposure: lowered the day sun (0.78→0.62), exposure (0.8→0.6) and ambient
(0.42→0.22), and darkened the sidewalk/street albedo tints. The like-for-like ground
luminance in `docs/screens/realism/2026-11-01-adopt/` went from 173/162/132 to 95/92/73
(concept targets 78/100/100). The entrance shot stays ~20 % brighter than the concept: a
residual, tracked in the follow-ups below.
- [x] Make the Evening/Late phases darker and lit by practical lights (marquee, lamps,
windows), and add wet-asphalt roughness at night, as in the rain-night concept. Evening is
now dusk (`sun 0.3→0.12`, `exposure 0.95→0.72`, dark violet sky), Late is near-dark, and
`WorldBuilder.set_phase()` swaps the street to the wet asphalt map in Late.
- [x] Choose the skyline per phase (day → `skyline_day`, Evening → `skyline_dusk`, Late →
`skyline_night`) and stop the scene fog from bleaching it. The backdrop is now a
`fog_disabled` shader, because Godot 4.3 has no `fog_enabled` on `StandardMaterial3D`. The
first dusk panorama was a red floodlit skyline that clashed with the Evening sky, so a
calmer prompt produced `skyline_dusk-v5` (blue-grey towers, lit windows).
- [x] Raise the backdrop plane (60 m → 90 m) and use 2 UV repeats so the skyline is a band with
proportions near the 2:1 panorama, not a thin strip at the horizon.
- [x] Add facade depth (plinth, string courses, cornice, brick pilasters, window glazing bars)
and replace the blob trees and shrubs with denser foliage meshes: `_shrub()` now builds
many small flat-shaded clumps plus outer clumps that break the silhouette
(`street_tree` 1472→2592 tris), with fresh AO maps for `street_tree`, `planter_box` and
`round_planter`.
- [x] Re-cut `awning_canvas` so its stripes align at the tile edge: `stripe_period()` measures the
repeat and `align_stripes()` resamples to a whole number of periods (edge crossfade smeared
the stripes before). `audit_striped_tile()` checks the wrap step against the harshest
interior step instead of the flat edge-difference rule.
- [x] Capture shots from the Joke Shop and the studio too: `capture_realism.gd` now runs five
stages (day, evening, late, joke_shop, studio) with 9 shots and a `--stages=` subset. The
venues share the Hollywood geometry, so those two compare lighting, not layout.
#### Step 7 — Adopt the winners
- [x] Track the validated picks in place (ADR-0014). `.gitignore` now ignores the candidate pool
and un-ignores `game_ready/v2/picks/`, where `scene_concepts.py promote` copies the eight
validated candidates under stable names (`skyline_{day,dusk,night}`, `brick_facade`,
`sidewalk_pavers`, `asphalt`, `asphalt_wet`, `awning_canvas`) plus normal maps.
- [x] Point `WorldBuilder` at the chosen v2 skylines (separate day/dusk/night textures through
the new `set_phase()`), and retune the tints per phase.
- [x] Switch `materials.gd` / `world_builder.gd` surface preloads to the chosen v2 tiles + normal
maps, keeping the current UV scales (`Materials.asphalt()` gained a `wet` variant). Updated
`test_materials.gd` (pick paths, wet vs dry) and `test_world.gd` (shader backdrop, panorama
per phase, wet street).
- [x] Regenerate `docs/screenshots/` with `capture_screenshots.gd` (11/11), then run
`screen_designs.py compare` (**11 good / 0 bad**) and `judge` (5 match, 2 review, 4 diff —
the diffs are HUD/mockup layout gaps from the "Visual alignment" section, not realism
regressions; the default flash-lite judge model is currently failing with a provider 400, so
the run used `--model google/gemini-2.5-flash`). `scene_concepts.py realism` on
`docs/screens/realism/2026-11-01-adopt/` (3 judge repeats per shot) gives a like-for-like
improvement on every sub-score: overall 0.8→1.0, lighting 0.8→1.0, materials 0.8→1.0,
texture 0.8→1.0, backdrop 0.6→1.6, and the Pillow metric gap 0.444→0.348.
- [x] Update `README.md` (tool usage + gallery), `MEMORY.md` and `docs/architecture.md`.
## Visual Alignment — UI Paths and Art Loader
Completed 2026-11-01.
### Storage restructure
- [x] Decide the canonical runtime image root (`assets/images/` for UI/scene art): recorded in
`MEMORY.md` and [ADR-0014](docs/adr/0014-runtime-image-root.md).
- [x] Move the reference mockups from `docs/screens/designs/` into the runtime root (keep `docs/screens/designs/` as a docs-only copy or replace it with the new path).
- [x] Document one convention for game vs reference vs development images across `assets/textures/`, `assets/images/`, `assets/models/generated/`, `docs/screenshots/`, and `docs/screens/designs/` (in `docs/architecture.md`, ADR-0014).
- [x] Re-run `godot --headless --path . --import` to regenerate `.import` sidecars after any move.
- [x] Confirm `.gitignore` still excludes development-only outputs (concepts, Blender sources, raw AI fabrics) under the new layout; Blender `.blend` sources are now untracked and ignored.
### Documentation path updates
- [x] Update `README.md` paths (screen-designs section, confirmed-assets links, the `assets/images/scenes` resize example).
- [x] Update `docs/architecture.md`, `PLAN.md`, and ADR references: docs/screens/designs/ is now explicitly the reference-only snapshot; runtime art lives in assets/images/ (ADR-0014).
- [x] Update `tools/screen_designs.py` docstrings and mark the old paths in `DONE.md` as historical.
### Code path updates
- [x] Point `tools/screen_designs.py generate` at the new design output path and keep `compare`/`judge` reading the right locations.
- [x] Update GDScript `preload()` paths if game textures move (no runtime material/model textures moved; UI art added under assets/images/).
- [x] Update capture output directories if captures relocate (none did; QA captures stay in docs/screenshots/).
### Load proposed interfaces + scene art from disk
- [x] Add a central UI/scene art loader (e.g. `src/game/ui/art.gd`) that loads the mockup-derived images from the new runtime root, with a procedural fallback when a file is missing.
- [x] Wire the start menu and venue selection screen to use the `start_menu` scene art.
- [x] Wire the pause overlay to `pause_overlay` scene art (backdrop + opaque panel cover).
- [x] Wire the settings panel to `settings_panel` art.
- [x] Wire the shift-review screen to `shift_review` art (backdrop + opaque panel cover).
- [x] Wire the wand popup to `wand_screening` art.
- [x] Style the radial menu and finish the remaining HUD chrome against their reference images.
### Verify against the mockups
- [x] Regenerate the staged captures and run `screen_designs.py compare` (11 good, 0 bad; verified in a clean HEAD worktree).
- [x] Run `screen_designs.py judge` and clear every `diff` verdict (7 match, 4 review, 0 diff).
- [x] Add the capture + compare step as a repeatable smoke so drift is caught early (`tools/check_screenshots.sh`).
## Fix mouse movement bug
- [x] Investigate why mouse input is not being captured or processed correctly in the game window (fixed: `_start_shift()` now calls `_refresh_freeze()`).
## Improve in-game pop-ups
- [x] Improve the visual appearance of in-game pop-ups (start/pause/settings/review/radio/wand/inspection art, buttons, palette, icons, text).
## Visual Alignment With Rendered Mockups
Completed 2026-11-01. Every screen with an AI reference now loads or matches its mockup: the
runtime image root (ADR-0014), the UI/scene art loader, the menus/panels/HUD, and — last — the
scene lighting, fog and post-processing, which had been held until the realism pass landed.
`screen_designs.py compare` **11 good / 0 bad**; judge on the two scene shots: `01-venue-overview`
**match** (0.90), `05-late-night-avenue` **review** (0.10), 0 diff. Lighting: morning sun 34°
elevation with a phase `sun_yaw`, warm day haze (`fog_color` 0.87/0.83/0.76, density 0.0034,
energy 0.5), stronger night glow, street albedo 0.62. Remaining differences (camera height,
marquee text, hand-authored tree shapes) are content, not grading.
### Visual alignment with rendered mockups
#### Align in-game visuals
- [x] Make the venue selection screen match the `start_menu` mockup (layout, palette, background).
- [x] Make the HUD match the `targeting_npc` / `escalation_hud` mockups (focus ring, escalation meter, log, hints).
- [x] Align the pause, settings, and shift-review panels to their mockups.
- [x] Align the radio and wand panels to their mockups.
- [x] Align the body-camera timeline to its mockup.
- [x] Align scene lighting, fog, and post-processing for `venue_overview` and `late_night_avenue`: lowered the morning sun to 34° (`sun_yaw` is now a phase value), warmed and thickened the day haze, raised the night glow, and brightened the street albedo. `judge` moved `01-venue-overview` to **match** (0.90) and `05-late-night-avenue` from diff to **review** (0.10); `compare` stays 11 good / 0 bad.
- [x] Fix the body-camera REC/clock overlay overlapping the shift/rep/crowd status in the top-right HUD.
+208 -2
View File
@@ -4,6 +4,20 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
## Decisions
- The visual-alignment path/loader/verification work is complete and recorded in `DONE.md`;
the only open visual task is scene lighting/fog, held until the current realism pass lands.
- 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.
- Graphics v0.6.0 plan (2026-11-01): NPCs move to Blender-authored skinned meshes
(`docs/plan-graphics-v0.6.md`, ADR-0013). `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`.
@@ -13,8 +27,100 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- 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.
- **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 + active passes (2026-11-01, moved from `TODO.md`):** v0.6.0 shipped — the Blender
character/gear/prop library is integrated, AO maps are baked and wired, and the environment
textures are lifted to 1024² with normal maps (see `DONE.md`). Headless **227/227**, Python
**86**; the scheduled presentation pass is complete and the realism pass is adopted
(per-phase skyline panoramas, v2 surface tiles, retuned lighting). Active in `TODO.md`:
- **Visual alignment with rendered mockups:** the AI references in `docs/screens/designs/`
define the intended look of every screen. The pass reorganizes generated-image storage,
loads the proposed UI/scene art at runtime (`src/game/ui/art.gd`, `assets/images/`), and
aligns the selection screens, HUD, and scene visuals with those mockups — verified with
`screen_designs.py compare` + `judge`.
- **Realism pass** (plan: `PLAN.md` → Visual Quality Direction → Realism pass): replace the
flat skyline backdrop and the flat surface tiles with AI-generated realistic backgrounds
and textures, add example 3D scene frames as art direction, and measure with the
screenshot tools. Rule: never overwrite an existing image — new outputs get new paths
(`next_free_path` -vN suffix). Raw → `assets/textures/ai/v2/raw/` (gitignored); candidates
→ `assets/textures/ai/game_ready/v2/` (gitignored pool, only validated picks tracked,
ADR-0014); scene refs → `docs/screens/concepts/`; trials → `docs/screens/realism/<run>/`.
Realism report 2026-11-01-v2 mean judge scores: backdrop 2.6 → 3.6 (v2 day + night
skyline) → **4.0** (v2 day + dusk skyline for the Evening phase); texture detail 1.2 →
1.6–1.75; overall 1.6 → 2.0. Scores swing ±1–2 between runs (trend only); the
`characters` score moved 3.0 → 1.0 with identical NPCs, showing the noise.
- **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. **Do not commit yet** — Phase 1
files are uncommitted; coordinate the commit with the Phase 1/3 agents.
**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.
**Foreign, left uncommitted:** a TODO section "Bug Fix NPCs walking backwards"
from another agent — do not touch or commit it.
- 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**.
@@ -29,7 +135,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
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/step-line-web-<ver>.zip`, and `publish_release.sh` uploads it. Covered by
`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
@@ -41,12 +147,108 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
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:** `google/gemini-2.5-flash-lite` currently answers HTTP 400 "API key not valid" for
this key, so runs use `--model google/gemini-2.5-flash`; 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.
- **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.36) + `smart_project` at 80°: glTF vertex
count includes UV-seam splits, so extra beveled detail quickly breaks 4000.
**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).
- **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.
## 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 were regenerated from HEAD in a clean worktree; the main working tree's `docs/screenshots` is being rewritten by the realism pass, so do not overwrite it from that run.
- `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
- **v0.3.0 pass (2026-10-31, uncommitted):** rendering, generated prop models, screen
@@ -266,7 +468,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
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 (`step-line.exe`); ICO must be
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
@@ -411,3 +613,7 @@ session shell; leftover PLAN verbs; crowd rubberneck; emergency aftermath; body-
sit/photo/film poses; real crowd audio; Joke Shop/Studio extra events; PLAN.md says GUT/GitHub.
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
## Uncommitted Workstreams
- A separate realism pass is editing `PLAN.md`, `README.md`, `.gitignore`, `src/game/materials.gd`, `tools/scene_concepts.py`, `tools/test_scene_concepts.py`, `src/test/capture_realism.gd`, `assets/textures/ai/game_ready/v2/`, `docs/screens/concepts|realism/`, and regenerated Blender planter/tree assets. Do not commit or revert these while that pass is active; verify game changes in a clean `git worktree` at HEAD when its unimported textures break parsing.
+58 -1
View File
@@ -1,4 +1,4 @@
# Game Plan: Step Line
# Game Plan: Gig Line
## Premise
@@ -399,6 +399,63 @@ The studio also has authorized guests and staff. They must either be recognized
- 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.
---
## Visual Quality Direction
The game prioritizes readable, well-lit, low-poly presentation over realism (GL Compatibility
renderer). The v0.6.0 visual pass moves presentation to a **Blender-authored asset library**:
skinned NPC character variants, head/back/chest/hand gear, and street/venue props, all carrying
baked albedo/normal/AO/ORM maps. A generator (`tools/blender_assets.py`) and manifest
(`assets/models/blender/manifest.json`) keep the assets reproducible; the game selects variants
by NPC role/outfit and keeps the procedural rig and primitive props as offline fallbacks. The
existing AI tools remain the source of art direction for fabric textures, palettes, and the
screen-design compare/judge gate.
The library targets 8 character variants (casual, jacket, coat, vest, dress, hoodie, firefighter,
guard), 18 gear pieces, and 20 props — including replacements for the current generated street
furniture, lamps, trees, doors, marquee, and a venue car. Integration must preserve the
`Rig/Head` focus anchor, seed-driven outfit determinism, collider/navigation footprints, and the
frame budget measured by `smoke_npc_budget.gd`. See
[`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md) and
[ADR-0013](docs/adr/0013-blender-authored-npc-characters.md).
### Realism pass
The target look is the realistic 3D-game frames in `docs/screens/designs/` and the larger
example set in `docs/screens/concepts/`: hazy, layered city depth, believable materials, and
warm practical lights at night. Readability still comes first. Silhouettes, the focus ring and
the HUD must stay clear, and the GL Compatibility renderer stays.
- **Backgrounds.** The flat, black-box skyline strip is retired in favour of AI-generated
panoramic skylines (Hollywood mid-rises, palms, hills) for day, dusk and night. Each one is a
transparent-sky 4:1 strip that tiles horizontally on the existing backdrop ring, and the night
variant has lit windows.
- **Textures.** Higher-detail seamless 1024² tiles with derived normal maps for brick, granite,
sidewalk pavers, asphalt (dry and wet), steps, awning canvas and stucco, at the current UV
scales.
- **Process.** `tools/scene_concepts.py` generates (never overwriting existing images),
processes, audits and judges. `src/test/capture_realism.gd` captures the same camera shots
with the current and the candidate assets, swapped at runtime, so a candidate is compared
before any game code changes. Candidates are adopted only when the realism report (Pillow
detail metrics + vision-model realism scores) improves and `screen_designs.py judge` shows no
new `diff`.
- **Storage.** Raw AI output: `assets/textures/ai/v2/raw/` (local, gitignored). Game-ready
candidates: `assets/textures/ai/game_ready/v2/`. Scene references: `docs/screens/concepts/`.
Trial captures and reports: `docs/screens/realism/<run>/`.
- **First result (2026-11-01).** Swapping in the v2 skyline and tiles raised the judge's mean
backdrop score from 2.6 to 4.0 and texture detail from 1.2 to ~1.7. Overall realism stayed at
~2/10; the limits were lighting and exposure, flat facades and blob foliage, not textures.
- **Adopted (2026-11-01).** The v2 picks are now the runtime skyline and surface set, the phase
lighting was retuned (exposure, darker dusk/late, practical lights, wet asphalt at night), the
backdrop is a fog-free 90 m panorama ring per phase, the facade gained plinth/string
courses/cornice/pilasters/glazing bars, and the library trees and shrubs are denser. Judge
scores on the like-for-like shots rose on every sub-score (overall 0.8 → 1.0, backdrop
0.6 → 1.6) with the metric gap down 0.444 → 0.348
(`docs/screens/realism/2026-11-01-adopt/`). The remaining gap to the concepts is the low-poly
character and prop fidelity, a visible flashlight beam at night, and the entrance still being
~20 % brighter than the concept (`TODO.md` → Realism follow-ups).
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.
+176 -24
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,26 +13,29 @@ 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) | |
*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, 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 |
| **Web (browser)** | [`step-line-web-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unzip and serve the folder from any static host (no special headers) |
| **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) |
**All releases:** <https://git.allucanget.biz/zwitschi/security-game/releases>
> Downloads are public (no sign-in required) since the host's instance-wide
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
>
> **Latest: v0.3.2** — every screen in the design catalogue now has an AI reference mockup.
> **Latest: v0.6.0** — Blender-authored character/gear/prop library with baked AO, higher-resolution environment textures, and phase-driven library props.
> Releases are built automatically when a `vX.Y.Z` tag is pushed
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
@@ -42,7 +45,7 @@ The central design goal is simple:
> 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
@@ -141,7 +144,7 @@ The architecture maps onto Godot 4 nodes, scenes, and custom resources as descri
- **NPC behavior:** Custom lightweight finite state machines driven by `NpcProfile` resources
- **UI and audio:** Godot `Control` nodes, themes, audio buses, and `AudioStreamPlayer` nodes
- **Art assets:** authored PNGs and the in-game generated set in `assets/textures/ai/game_ready/`; street-furniture models in `assets/models/generated/` build from `tools/model_pipeline.py`; structural geometry stays procedural (`WorldBuilder`). The legacy `tools/generate_textures.py --ai` batch remains available.
- **Asset tooling:** `tools/asset_pipeline.py` generates role-aware assets through OpenRouter and provides crop, resize, seamless-tile, and image-audit commands, including gameplay screenshot checks. `tools/model_pipeline.py` builds and audits the prop `.glb` models.
- **Asset tooling:** `tools/asset_pipeline.py` generates role-aware assets through OpenRouter (`--variants N` produces alternative images) and provides crop, resize, seamless-tile, and image-audit commands, including gameplay screenshot checks. `tools/model_pipeline.py` builds and audits the street-prop `.glb` models; `tools/npc_pipeline.py` builds and audits the skinned NPC base model.
- **Testing:** GUT (Godot Unit Test) in headless mode
- **Source control:** git on a Gitea host with Gitea Actions (`.gitea/workflows/build.yml`) for headless tests and desktop release builds
- **Targets:** Windows, Linux, and macOS desktop first; web later
@@ -169,7 +172,7 @@ Crop uses the image center by default; `--x` and `--y` select an explicit crop o
```bash
python3 tools/asset_pipeline.py crop assets/textures/source.png assets/textures/crop.png --width 256 --height 128
python3 tools/asset_pipeline.py resize assets/textures/source.png assets/images/backgrounds/scene.png --width 1920 --height 1080 --fit cover
python3 tools/asset_pipeline.py resize assets/textures/source.png assets/images/scenes/scene.png --width 1920 --height 1080 --fit cover
python3 tools/asset_pipeline.py seamless assets/textures/rough_tile.png assets/textures/tile.png
```
@@ -177,7 +180,7 @@ Audit accepts image files or directories. `--role auto` infers common roles from
```bash
python3 tools/asset_pipeline.py audit assets/textures
python3 tools/asset_pipeline.py audit assets/images/backgrounds --role background --json
python3 tools/asset_pipeline.py audit assets/images/scenes --role background --json
python3 -m unittest discover -s tools -p 'test_*.py'
```
@@ -218,12 +221,133 @@ Surface materials share one definition in `src/game/materials.gd`: named
roughness/metallic profiles per surface, plus the environment tune-up (ACES
tonemapping, glow, fog, colour grading) that the phase lighting applies.
### NPC character model
NPCs are moving from the procedural capsule rig to a skinned, UV-mapped base
character authored in Blender ([ADR-0013](docs/adr/0013-blender-authored-npc-characters.md)).
`tools/npc_pipeline.py` builds `assets/models/generated/npc_base.glb` plus the
per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its
five material slots from the seed-driven outfit, and drives the bones from the
existing animation code; the procedural rig remains the offline fallback (see
the [graphics plan](docs/plan-graphics-v0.6.md)).
```bash
# Print the frozen contract (slots, bone names, triangle budget)
python3 tools/npc_pipeline.py list
# Rebuild the skinned base mesh + neutral maps in Blender and validate it
flatpak run org.blender.Blender --background --python "$PWD/tools/npc_pipeline.py" -- build
python3 tools/npc_pipeline.py audit
# Layer the AI clothing albedos (shirt/trousers) over the procedural weave, and
# derive per-fabric outfit colours for the avatar palette
python3 tools/npc_pipeline.py prepare
python3 tools/npc_pipeline.py palette
```
`prepare` resizes the AI fabric tiles (kept locally in `assets/textures/npc/ai/`,
which is gitignored) onto the `cloth`/`pants` albedos; `build` bakes them into the
`.glb` and regenerates the procedural normal + packed ORM maps. `palette` writes
`assets/textures/npc/palette.json` using `model_pipeline.dominant_colors`.
### Blender asset library (loaded by the game)
`tools/blender_assets.py` models a larger library procedurally in Blender for the
v0.6.0 integration: **8 skinned NPC characters** (casual, jacket, coat, hi-vis vest,
dress, hoodie, firefighter, guard), each a drop-in for `npc_base.glb` (same bones,
joint positions and tint slots); **18 gear pieces** authored at head/hand/back/chest
attach points (caps, hood, helmet, backpack, SCBA tank, lanyard, phone, cup,
clipboard, umbrella, camera, sign, flashlight, wand, radio, …); and **20 props**
(octagonal bollard and bin, slatted bench, hydrant, news box, planters, street lamp,
street and palm trees, A-frame sign, wall lantern, cone, crowd barrier, stanchion,
handrail, podium, double door, marquee, sedan).
`NpcAvatar` selects a character by role/seed, tints the five contract slots, and
attaches gear at the documented points; `WorldBuilder` loads the library props
with colliders/avoidance and drives their emissive parts from the day/night
cycle. The single `npc_base.glb` and primitive props remain offline fallbacks.
Contract and integration notes: [`docs/blender-assets.md`](docs/blender-assets.md).
| Characters | Props |
| --- | --- |
| ![characters](docs/blender_previews/sheet_npc.jpg) | ![props](docs/blender_previews/sheet_props.jpg) |
```bash
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render
python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg
python3 tools/blender_assets.py audit # no Blender needed
godot --headless --path . --import
```
### Confirmed game assets
Everything below is audited and shipped in the game; development-only outputs
(AI concept sheets, Blender sources, raw AI fabrics) are gitignored.
| | |
| --- | --- |
| ![brick facade](assets/textures/ai/game_ready/v2/picks/brick_facade.png) | ![sidewalk pavers](assets/textures/ai/game_ready/v2/picks/sidewalk_pavers.png) |
| ![asphalt](assets/textures/ai/game_ready/v2/picks/asphalt.png) | ![awning](assets/textures/ai/game_ready/v2/picks/awning_canvas.png) |
| ![skyline day](assets/textures/ai/game_ready/v2/picks/skyline_day.png) | ![skyline night](assets/textures/ai/game_ready/v2/picks/skyline_night.png) |
- **Surface textures:** the v2 picks above (brick, sidewalk pavers, dry and wet asphalt, awning canvas) plus their normal maps, loaded by `src/game/materials.gd` and `WorldBuilder`; the three skyline panoramas are switched per shift phase. The first-generation tiles and `assets/textures/skyline.png` are no longer loaded.
- **UI art:** `assets/textures/ai/game_ready/icons/` (flashlight, door lock, wand, REC) and `assets/textures/ai/game_ready/cards/` (ID, wristband, ticket, credential, guest list), loaded by `src/game/ui/hud.gd`.
- **Generated models:** `assets/models/generated/*.glb` — bollard, bench, trash bin, planter, newspaper box, hydrant, and the NPC base — plus their colour atlases.
- **NPC material maps:** `assets/textures/npc/*_{albedo,normal,orm}.png` and `palette.json`.
Audit them all:
```bash
python3 tools/asset_pipeline.py audit assets/textures/ai/game_ready
python3 tools/model_pipeline.py audit
python3 tools/npc_pipeline.py audit
```
### Realism pass: scene concepts, skylines and textures
The skyline, surface tiles and phase lighting were rebuilt against AI concept frames. The
concepts live in [`docs/screens/concepts/`](docs/screens/concepts/README.md); the validated
picks the game loads are in `assets/textures/ai/game_ready/v2/picks/` (day/dusk/night
panoramas, brick, sidewalk pavers, dry and wet asphalt, awning canvas, each with a normal
map).
`tools/scene_concepts.py` generates and processes new candidates, promotes the validated ones
to stable names, audits them, and scores captures against the concepts. It never overwrites an
image: new files get a `-vN` suffix, and `process` skips existing candidates unless `--new`.
```bash
python3 tools/scene_concepts.py generate --group scene # or backdrop / texture, --variants N
python3 tools/scene_concepts.py process && python3 tools/scene_concepts.py audit
python3 tools/scene_concepts.py promote # validated picks -> v2/picks/
godot --headless --path . --import
# A/B a candidate without touching game code: the same cameras, textures swapped at runtime.
godot --path . --script res://src/test/capture_realism.gd -- --out=res://docs/screens/realism/<run>/after
godot --path . --script res://src/test/capture_realism.gd -- --out=res://docs/screens/realism/<run>/try \
--swap=res://assets/textures/ai/game_ready/v2/picks/brick_facade.png=res://assets/textures/ai/game_ready/v2/brick_facade-v2.png
# Score before/after against the concepts (Pillow detail metrics + vision judge).
python3 tools/scene_concepts.py realism --run docs/screens/realism/<run> --judge \
--model google/gemini-2.5-flash --repeats 3
```
`capture_realism.gd` runs five stages (day, evening, late, Joke Shop, studio); `--stages=day,late`
limits it while tuning. The latest run,
[`docs/screens/realism/2026-11-01-adopt/`](docs/screens/realism/2026-11-01-adopt/report.md),
compares the pre-adoption captures with the current build: on the shots both sets captured the
judge's like-for-like means rose on every sub-score (overall 0.8 → 1.0, backdrop 0.6 → 1.6) and
the Pillow metric gap fell 0.444 → 0.348.
![realism run](docs/screens/realism/2026-11-01-adopt/sheet.jpg)
### Screen design references
Screen mockups and screenshot comparison use `tools/screen_designs.py`. It turns the
screen descriptions in `docs/` (README captions, ADRs 0007–0010, `PLAN.md`, and
`architecture.md`) into reference images under `docs/screens/designs/`, then uses
those images as the expected state to check the game's captures.
`architecture.md`) into runtime reference art under `assets/images/ui/` (with a
reference-only snapshot in `docs/screens/designs/`), then uses those images as
the expected state to check the game's captures.
Each screen is tagged `scene` (a realistic first-person 3D world view) or `ui`
(an in-game panel overlaid on a softened scene), and the generator appends a
@@ -246,6 +370,9 @@ python3 tools/screen_designs.py generate --draft --only wand --force
# (requires OPENROUTER_API_KEY)
python3 tools/screen_designs.py generate
# Prop gallery (studio line-up) for close-range prop checks
godot --path . --script res://src/test/capture_prop_gallery.gd
# Compare docs/screenshots/*.png against the designs
python3 tools/screen_designs.py compare
python3 tools/screen_designs.py compare --json
@@ -258,12 +385,12 @@ python3 tools/screen_designs.py judge --only 03- # one capture
python3 tools/screen_designs.py judge --dry-run # build requests, no network
```
The catalogue covers 19 screens: the six README captures plus the start menu,
The catalogue covers 21 screens: the eleven README captures plus the start menu,
pause overlay, settings, shift review, radial menu, emergency radio, wand
screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival,
body-camera timeline, and the escalation HUD. All 19 have an AI reference in
`docs/screens/designs/`; the six screens with staged captures are also compared
or judged against the running game.
body-camera timeline, and the escalation HUD. All 21 have an AI reference in
`assets/images/ui/` (snapshot in `docs/screens/designs/`); the eleven screens with
staged captures are also compared or judged against the running game.
`compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank
frame, and large layout/palette/detail distance. It is a regression gate, not a
@@ -279,6 +406,10 @@ default 0.6 — expected when a concept mockup is more stylised than the game),
`compare` after regenerating the game screenshots with
`src/test/capture_screenshots.gd`.
For a one-command drift check, run `tools/check_screenshots.sh`. It renders the
staged captures and prop gallery into `docs/screenshots/` (using `xvfb-run` when
no display is available) and then runs `screen_designs.py compare`.
### Setup
1. Download the Godot 4.3 editor (official build) and put `godot` on your PATH.
@@ -311,6 +442,15 @@ godot --headless --path . --script res://src/test/smoke_session_shell.gd
# PLAN-verb smoke: recognize, request reason, body camera, warning, blocking
godot --headless --path . --script res://src/test/smoke_plan_verbs.gd
# Prop-collision smoke (windowed): guard stops at a bench, guest routes around it
godot --path . --script res://src/test/smoke_props.gd
# NPC budget smoke: 16 full-rig avatars against the documented frame budget
godot --headless --path . --script res://src/test/smoke_npc_budget.gd
# Movement smoke (windowed): steps, precision walk, footsteps and landing
godot --path . --script res://src/test/smoke_movement.gd
# Crowd smoke: rubbernecking on aggression, panic clearing the entrance
godot --headless --path . --script res://src/test/smoke_crowd.gd
@@ -331,6 +471,11 @@ The unit test runner is the project's own lightweight harness (no editor plugin
### Controls
Keyboard and mouse are the default; a gamepad works without configuration (left stick moves,
right stick looks, A opens/confirms the wheel, B cancels, X inspects, Y calls the radio, LB walks
slowly, RB uses the wand). Keys and pad buttons can be rebound in Settings, and the remaps persist
in `user://settings.json`. The HUD legend switches to gamepad hints when a pad event arrives.
| Input | Action |
| --- | --- |
| W/A/S/D + mouse | Move and look |
@@ -371,6 +516,8 @@ The full arc42-style architecture documentation lives in [`docs/architecture.md`
- [`MEMORY.md`](MEMORY.md) — Persistent session notes.
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
- [`docs/adr/`](docs/adr/) — Architecture Decision Records.
- [`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md) — Graphics-quality plan: Blender-authored NPC characters.
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
## Development Status
@@ -379,7 +526,7 @@ Implemented so far:
- Godot 4.3 project scaffold, input map, procedural world builder
- Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`
- Three authored scenarios (Hollywood Boulevard building, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools
- Headless unit tests: **182 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material)
- Headless unit tests: **227 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings)
- Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event
- First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review
- Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`)
@@ -402,12 +549,17 @@ Implemented so far:
- Visual polish pass (v0.2.0): phase-based lighting (sun/sky/ambient per shift phase), street lamps with night-mode bulbs and omni lights, glowing marquee signage, crosswalk, planters and low-poly street trees, NPC height/brightness variety, denser routine traffic — day and late-night screenshot sets
- Assets pass: authored textures in `assets/textures/` (brick facade, concrete, asphalt, awning, skyline, vignette, HUD icons, access-card art) generated by `tools/generate_textures.py`, soft sun shadows, humanoid NPC rigs with archetype props + procedural walk bob and attention head-turn, inspection artifact cards, escalation vignette, mode icons, and a body-camera overlay (REC + shift clock)
- Rendering pass (v0.3.0 work, [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md)): one material module with roughness/metallic profiles per surface, ACES tonemapping, glow, aerial fog and colour grading driven by the shift phase, correctly scaled brick, framed facade windows that light up at night, door trim and handles, a curb and lane dashes, and the glowing marquee/lamp bulbs. NPCs gained per-role clothing palettes, caps/hoods/backpacks/sunglasses/lanyards, hands and shoes, and an arm/leg walk swing
- NPC characters (v0.5.0): articulated limb rigs, layered outfits, hand items, face/hair variety, idle behaviour (phone, clipboard, crossed arms, look-around), escalation gestures, readable reaction symbols, door-queue offsets, and three LOD tiers with a billboard impostor
- Player movement (v0.5.0): separate acceleration/braking, step-up assist, head bob and landing dip (toggleable), precision-walk FOV/chip/tick, surface-aware footsteps, gamepad support, and persistent key/button remapping in settings
- Prop collision and placement (v0.4.0): every prop fits a static collider from its mesh bounds and publishes a navigation obstacle, so the guard and guests cannot walk through benches, bins, or bollards; placements are grouped into zones with a validator, and deterministic variants add rotation, scale, and small clutter
- Prop atlas (v0.3.1): `tools/model_pipeline.py` packs each prop's palette into one power-of-two colour atlas plus a metallic/roughness atlas and emits a single material with per-vertex UVs, so a prop is one draw material rather than one per part; `audit` enforces the contract
- Generated prop models (v0.3.0 work): `tools/model_pipeline.py` builds bollards, benches, bins, planters, newspaper boxes and a hydrant as `.glb` props with per-part materials, loaded by `WorldBuilder` with a procedural fallback; the optional AI concept step (`model_pipeline.py concept`) supplies per-prop colour palettes
- Web build: `build/step-line-web-<version>.zip` (Godot's non-threaded web template, no COOP/COEP headers needed) built by `tools/build_releases.sh` and published with the desktop artifacts; serve the unzipped folder from any static host
- Environment pass (v0.6.0): 1024² brick/concrete/asphalt with derived tangent normal maps, 256² baked AO maps for every Blender character/gear/prop, and library props whose bulbs/LEDs/sign faces follow the day/night phase
- NPC characters + library (v0.6.0): `NpcAvatar` loads one of 8 Blender-authored skinned variants (role/seed selected) with five tintable material slots, `Rig/Head` focus anchor, and attach-point gear; `npc_base.glb` and the procedural rig remain fallbacks. `tools/blender_assets.py` builds/audits the character, gear, and 20-prop library; `WorldBuilder` loads the replacements and new street dressing (lamp, trees, palms, planters, barriers, podium, doors, marquee, sedan, handrail) with colliders, navigation obstacles, and phase-driven emissives
- Web build: `build/gig-line-web-<version>.zip` (Godot's non-threaded web template, no COOP/COEP headers needed) built by `tools/build_releases.sh` and published with the desktop artifacts; serve the unzipped folder from any static host
- Aim-free selection pass (v0.3.0 work, [ADR-0011](docs/adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work
Not yet implemented: live 3D body-camera replay, editor-based GUT integration, and crowd performance profiling/LOD (see `TODO.md`).
Not yet implemented: a desktop-only Forward+ preset (the renderer stays GL Compatibility for Web parity), live 3D body-camera replay, editor-based GUT integration, and crowd performance profiling/LOD (see `TODO.md`).
### Smoke Test Checklist
@@ -417,7 +569,7 @@ Not yet implemented: live 3D body-camera replay, editor-based GUT integration, a
4. The guard's camera sits at eye height and the guard stands on the landing, not inside the building.
5. Walking into the steps carries the guard up onto the landing (steps have colliders).
6. The skyline backdrop shows silhouettes against the sky, not an opaque black slab.
7. The `STEP LINE` marquee is readable from the sidewalk.
7. The `GIG LINE` marquee is readable from the sidewalk.
8. NPCs spawn and approach the entrance during the first minutes.
9. Looking at an NPC inside the focus cone shows a blue focus circle in front of the face, the person's name, and the HUD target line.
10. `Space` or left click opens the action wheel; `A`/`D` highlight a slice, `Space` confirms it, `Esc`/right click cancels without a log entry.
+16 -5
View File
@@ -1,15 +1,26 @@
# TODO
Godot 4.3 prototype. Latest release **v0.3.2** (mockup set). Headless suite **182/182** plus
**53** Python tooling tests; the session, PLAN-verb, crowd, emergency, timeline, audio, scenario-content, and
windowed focus/radial smokes pass. Anonymous downloads are enabled on the host.
## Realism follow-ups (v0.6.1 candidates)
All planned backlog sections are complete (see `DONE.md`). Optional future ideas live below.
- [x] Fixed ground exposure for the entrance view: the morning sun now sits at 34° elevation
instead of 50°, so the facade shades the near sidewalk and props cast long shadows. Ground
luminance went 95 → **83** against the concept's 78 (it was 173 before the realism pass),
with the boulevard at 82 and the street at 81.
- [-] Make the flashlight read in the dark: the `06-night-flashlight` concept shows a visible
beam, but a Compatibility spot light only brightens the near wall. A cheap fake cone (an
additive mesh or a light-shaft quad) would sell it, and the night mockup expects it.
- [ ] NPC skin/clothing still reads as low-poly next to the concepts (judge `characters` ≈ 0–1).
The Blender library has the budgets for one more detail pass (fingers, sleeves, faces).
- [x] Re-check the `screen_designs.py judge` default model (`google/gemini-2.5-flash-lite`): on
2026-11-01 it returned 7 match / 4 review / 0 diff without a provider error; `--model`
override is documented in `README.md`.
- [ ] Add the two venue stages to the committed gallery (they are capture-only today) and give
the Joke Shop and studio their own geometry so their concept comparisons are geometric,
not just lighting.
## Future ideas (optional, not scheduled)
- [ ] Live 3D body-camera replay (the timeline is text-only today).
- [ ] LOD/culling for NPCs if `MAX_LIVE_NPCS` grows past the measured 16-avatar budget.
- [ ] 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
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: 58 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 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: 68 KiB

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: 56 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: 58 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: 29 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: 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

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