Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d872034388 | ||
|
|
d8cfddbf89 | ||
|
|
d513e0b82c | ||
|
|
7efb5024ec | ||
|
|
a231f7588a | ||
|
|
3989d7b2e5 | ||
|
|
0eb17e3bb4 | ||
|
|
a7daae8a14 | ||
|
|
3f4a8e5f9f | ||
|
|
91b10e0f94 | ||
|
|
4943dbff16 | ||
|
|
b24d0aeb61 | ||
|
|
a27e8cb74c | ||
|
|
2a4e0d1998 | ||
|
|
bb40a98537 | ||
|
|
a3e90accdd | ||
|
|
3025ba1cef | ||
|
|
a5e7b3c013 | ||
|
|
f2863abeac | ||
|
|
7f92b8861d | ||
|
|
afa1d6beec | ||
|
|
f763cff744 | ||
|
|
72b9378e69 | ||
|
|
74f14014b2 | ||
|
|
a2a0ded527 | ||
|
|
922a990c7a | ||
|
|
f0cec192a7 | ||
|
|
2dba562c52 | ||
|
|
afd086ba11 | ||
|
|
b4d189c325 | ||
|
|
6684d36739 | ||
|
|
3f4ac7e9aa | ||
|
|
294ac17333 | ||
|
|
e2c2a0bf4c | ||
|
|
1b976bf351 |
No files matched your search
@@ -84,19 +84,19 @@ jobs:
|
|||||||
# NOTE: on Linux, makensis uses -D defines (the /D form is Windows-only
|
# 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
|
# and is rejected as a script path), and it resolves relative paths
|
||||||
# against the script directory — pass absolute paths from the workspace.
|
# 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 \
|
makensis \
|
||||||
-DVERSION="$VERSION" \
|
-DVERSION="$VERSION" \
|
||||||
-DARTIFACT="$PWD/build/step-line-windows-$VERSION.exe" \
|
-DARTIFACT="$PWD/build/gig-line-windows-$VERSION.exe" \
|
||||||
-DICON="$PWD/packaging/windows/step-line.ico" \
|
-DICON="$PWD/packaging/windows/gig-line.ico" \
|
||||||
-DOUT="$PWD/build/step-line-setup-$VERSION.exe" \
|
-DOUT="$PWD/build/gig-line-setup-$VERSION.exe" \
|
||||||
packaging/windows/step-line.nsi
|
packaging/windows/gig-line.nsi
|
||||||
|
|
||||||
- name: Verify Windows installer contents
|
- name: Verify Windows installer contents
|
||||||
run: |
|
run: |
|
||||||
VERSION="$(sed -n 's/.*config\/version="\([^"]*\)".*/\1/p' project.godot)"
|
VERSION="$(sed -n 's/.*config\/version="\([^"]*\)".*/\1/p' project.godot)"
|
||||||
ls -la build/step-line-setup-$VERSION.exe
|
ls -la build/gig-line-setup-$VERSION.exe
|
||||||
7z l build/step-line-setup-$VERSION.exe | grep -E "step-line.exe|uninstall.exe"
|
7z l build/gig-line-setup-$VERSION.exe | grep -E "gig-line.exe|uninstall.exe"
|
||||||
|
|
||||||
- name: Package Linux AppImage
|
- name: Package Linux AppImage
|
||||||
run: |
|
run: |
|
||||||
@@ -106,13 +106,16 @@ jobs:
|
|||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: step-line-build
|
name: gig-line-build
|
||||||
path: build/*
|
path: build/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Publish Gitea release (tags only)
|
- name: Publish Gitea release (tags only)
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
env:
|
env:
|
||||||
|
# An explicit repo secret wins. Without one, publish_release.sh reuses
|
||||||
|
# the local git extraheader that actions/checkout configures for the job
|
||||||
|
# (the runner's own credentials), so tag builds still publish.
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
VERSION: ${{ github.ref_name }}
|
VERSION: ${{ github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -44,3 +44,23 @@ user://
|
|||||||
build/
|
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
|
||||||
@@ -163,7 +163,7 @@ Completed task sections moved from `TODO.md`. Sections are moved only when every
|
|||||||
applied on phase change and shift start.
|
applied on phase change and shift start.
|
||||||
- [x] Street lamps: poles with emissive bulbs + OmniLights on the sidewalk;
|
- [x] Street lamps: poles with emissive bulbs + OmniLights on the sidewalk;
|
||||||
`WorldBuilder.set_night_mode` switches bulbs/lights on at night.
|
`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] Street dressing: crosswalk stripes, planters, and low-poly trees.
|
||||||
- [x] NPC visual variety: per-seed height/brightness variation (deterministic LCG).
|
- [x] NPC visual variety: per-seed height/brightness variation (deterministic LCG).
|
||||||
- [x] Denser routine traffic (tourists/performers/influencers/trespassers).
|
- [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
|
- [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).
|
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
|
- [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.
|
`packaging/windows/gig-line.ico` and `packaging/linux/gig-line.png` via the generator.
|
||||||
- [x] NSIS script `packaging/windows/step-line.nsi` (installs `Step Line.exe`, uninstaller,
|
- [x] NSIS script `packaging/windows/gig-line.nsi` (installs `Gig Line.exe`, uninstaller,
|
||||||
shortcuts, registry entry; `makensis /DVERSION=… /DARTIFACT=…`).
|
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`).
|
`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).
|
ran it headless (0 script errors).
|
||||||
- [x] CI: NSIS + AppImage steps added to `.gitea/workflows/build.yml` build job; publish glob
|
- [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).
|
already uploads `build/gig-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`
|
- [x] CI run 1110 verified end-to-end (v0.2.1): NSIS installer `gig-line-setup-0.2.1.exe`
|
||||||
built, `7z l` shows `step-line.exe` + `uninstall.exe`, AppImage built (33.9 MB),
|
built, `7z l` shows `gig-line.exe` + `uninstall.exe`, AppImage built (33.9 MB),
|
||||||
release `v0.2.1` published, `Job succeeded`.
|
release `v0.2.1` published, `Job succeeded`.
|
||||||
- Gotchas fixed on the way: makensis needs `-D` defines (not `/D`) and absolute paths
|
- 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
|
(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).
|
PNG-compressed ICOs NSIS rejects).
|
||||||
|
|
||||||
## 🎮 Assets & detailed graphics pass (textures, rigs, UI feedback)
|
## 🎮 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
|
seamless concrete, asphalt, awning, skyline, vignette, HUD icons, access-card art — all
|
||||||
≤1024px and power-of-two; imported under `assets/textures/`.
|
≤1024px and power-of-two; imported under `assets/textures/`.
|
||||||
- [x] Environment: textured facade + glass door, concrete sidewalk, asphalt street, striped
|
- [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,
|
- [x] NPCs: humanoid rig (torso/head/limbs) with archetype props (camera, delivery box, hat,
|
||||||
sign, backpack), procedural walk bob, head-turns-toward-guard cue.
|
sign, backpack), procedural walk bob, head-turns-toward-guard cue.
|
||||||
- [x] UI/feedback: inspection artifact cards, mode icons (flashlight/door/wand), escalation
|
- [x] UI/feedback: inspection artifact cards, mode icons (flashlight/door/wand), escalation
|
||||||
@@ -319,7 +319,7 @@ Completed in the working tree (uncommitted with the control pass). Headless **74
|
|||||||
to the entrance.
|
to the entrance.
|
||||||
- [x] Wand shortcut is `C`, not `W`. Tests: `src/test/tests/test_access_loadout.gd`.
|
- [x] Wand shortcut is `C`, not `W`. Tests: `src/test/tests/test_access_loadout.gd`.
|
||||||
|
|
||||||
## Player control, focus ring, and radial action menu
|
## Player control, focus circle, and radial action menu
|
||||||
|
|
||||||
Completed 2026-10-31 with ADR-0007–0010. Headless **77/77**, plus a windowed smoke
|
Completed 2026-10-31 with ADR-0007–0010. Headless **77/77**, plus a windowed smoke
|
||||||
(`src/test/smoke_focus_radial.gd`) that opens the wheel and saves screenshots.
|
(`src/test/smoke_focus_radial.gd`) that opens the wheel and saves screenshots.
|
||||||
@@ -334,13 +334,13 @@ Completed 2026-10-31 with ADR-0007–0010. Headless **77/77**, plus a windowed s
|
|||||||
- [x] `cancel` (Esc / RMB) closes the top modal first; with nothing open, Esc toggles mouse
|
- [x] `cancel` (Esc / RMB) closes the top modal first; with nothing open, Esc toggles mouse
|
||||||
capture and RMB does nothing.
|
capture and RMB does nothing.
|
||||||
|
|
||||||
### Focus and head ring
|
### Focus and focus circle
|
||||||
|
|
||||||
- [x] Focus cone in `src/game/npc_focus.gd` (~10 m, ~10°). Closest-to-forward wins; occlusion
|
- [x] Focus cone in `src/game/npc_focus.gd` (~10 m, ~10°). Closest-to-forward wins; occlusion
|
||||||
ray skips building hits. `Tab` still cycles 12 m.
|
ray skips building hits. `Tab` still cycles 12 m.
|
||||||
- [x] 8-frame miss grace so walk-bob does not drop focus.
|
- [x] 8-frame miss grace so walk-bob does not drop focus.
|
||||||
- [x] Billboarded torus on `Rig/Head`; gold idle, brighter while the radial is open; `Label3D`
|
- [x] Billboarded blue circle in front of the face on `Rig/Head`; brighter while the radial is
|
||||||
`display_name`. HUD target line uses the display name, not the node name.
|
open; `Label3D` `display_name`. HUD target line uses the display name, not the node name.
|
||||||
- [x] Screen-center crosshair.
|
- [x] Screen-center crosshair.
|
||||||
|
|
||||||
### Radial HUD
|
### Radial HUD
|
||||||
@@ -364,7 +364,7 @@ Completed 2026-10-31 with ADR-0007–0010. Headless **77/77**, plus a windowed s
|
|||||||
|
|
||||||
- [x] Named input map: `action_menu` (Space/LMB), `confirm` (Space/Enter), `cancel`
|
- [x] Named input map: `action_menu` (Space/LMB), `confirm` (Space/Enter), `cancel`
|
||||||
(Esc/RMB), `inspect` (E), `wand` (C). `GameManager` uses `is_action_pressed`.
|
(Esc/RMB), `inspect` (E), `wand` (C). `GameManager` uses `is_action_pressed`.
|
||||||
- [x] Windowed smoke: focus ring + name, wheel open/step/confirm/cancel
|
- [x] Windowed smoke: focus circle + name, wheel open/step/confirm/cancel
|
||||||
(`src/test/smoke_focus_radial.gd`, exits non-zero on failure).
|
(`src/test/smoke_focus_radial.gd`, exits non-zero on failure).
|
||||||
- [x] README controls table, smoke checklist, test count, implemented/not-yet lists.
|
- [x] README controls table, smoke checklist, test count, implemented/not-yet lists.
|
||||||
|
|
||||||
@@ -405,7 +405,6 @@ screenshots.
|
|||||||
|
|
||||||
Completed 2026-10-31 (working tree). Headless **88/88**; `src/test/smoke_session_shell.gd` and the windowed focus/radial smoke both pass.
|
Completed 2026-10-31 (working tree). Headless **88/88**; `src/test/smoke_session_shell.gd` and the windowed focus/radial smoke both pass.
|
||||||
|
|
||||||
|
|
||||||
- [x] The skyline backdrop was removed: `assets/textures/ai/game_ready/skyline.png` has an
|
- [x] The skyline backdrop was removed: `assets/textures/ai/game_ready/skyline.png` has an
|
||||||
opaque black background, so the planes rendered as black slabs with window dots. Re-add
|
opaque black background, so the planes rendered as black slabs with window dots. Re-add
|
||||||
the backdrop only with a transparent asset or a keyed shader.
|
the backdrop only with a transparent asset or a keyed shader.
|
||||||
@@ -413,8 +412,8 @@ 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.
|
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
|
- [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.
|
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 ring and name tag are small at 2–3 m. Consider a larger torus / font after a
|
- [x] Focus circle and name tag are small at 2–3 m. Consider a larger circle / font after a
|
||||||
real playtest.
|
real playtest.
|
||||||
- [x] NPC crowd cap (`MAX_LIVE_NPCS`) silently drops routine traffic at 16. Add a short
|
- [x] NPC crowd cap (`MAX_LIVE_NPCS`) silently drops routine traffic at 16. Add a short
|
||||||
despawn/lifetime for ordinary guests so the entrance does not fill up with standers.
|
despawn/lifetime for ordinary guests so the entrance does not fill up with standers.
|
||||||
@@ -423,7 +422,6 @@ Completed 2026-10-31 (working tree). Headless **88/88**; `src/test/smoke_session
|
|||||||
|
|
||||||
Completed 2026-10-31 (working tree). Headless **88/88**; `src/test/smoke_session_shell.gd` and the windowed focus/radial smoke both pass.
|
Completed 2026-10-31 (working tree). Headless **88/88**; `src/test/smoke_session_shell.gd` and the windowed focus/radial smoke both pass.
|
||||||
|
|
||||||
|
|
||||||
The game boots into a live 900 s shift. `Save` declares `settings.json` and never uses it.
|
The game boots into a live 900 s shift. `Save` declares `settings.json` and never uses it.
|
||||||
`V` cycles venues by restarting the shift.
|
`V` cycles venues by restarting the shift.
|
||||||
|
|
||||||
@@ -439,7 +437,6 @@ The game boots into a live 900 s shift. `Save` declares `settings.json` and neve
|
|||||||
|
|
||||||
Completed 2026-10-31 (working tree).
|
Completed 2026-10-31 (working tree).
|
||||||
|
|
||||||
|
|
||||||
- [x] README controls table, smoke checklist, test count, and implemented/not-yet lists
|
- [x] README controls table, smoke checklist, test count, and implemented/not-yet lists
|
||||||
refreshed for the control pass (77 tests).
|
refreshed for the control pass (77 tests).
|
||||||
- [x] `PLAN.md` Tech Stack / Planned Commands still say GUT and GitHub Actions; the project
|
- [x] `PLAN.md` Tech Stack / Planned Commands still say GUT and GitHub Actions; the project
|
||||||
@@ -450,7 +447,6 @@ Completed 2026-10-31 (working tree).
|
|||||||
|
|
||||||
Completed 2026-10-31 (working tree). Headless **98/98**; `src/test/smoke_plan_verbs.gd` exercises all five verbs.
|
Completed 2026-10-31 (working tree). Headless **98/98**; `src/test/smoke_plan_verbs.gd` exercises all five verbs.
|
||||||
|
|
||||||
|
|
||||||
- [x] Eye contact as a Level 1 action (PLAN / glossary). Distinct from raise-hand. Add a
|
- [x] Eye contact as a Level 1 action (PLAN / glossary). Distinct from raise-hand. Add a
|
||||||
radial slice if it ships.
|
radial slice if it ships.
|
||||||
- [x] "I recognize this person" is a player decision recorded as evidence, not a pre-attached
|
- [x] "I recognize this person" is a player decision recorded as evidence, not a pre-attached
|
||||||
@@ -466,7 +462,6 @@ Completed 2026-10-31 (working tree). Headless **98/98**; `src/test/smoke_plan_ve
|
|||||||
|
|
||||||
Completed 2026-10-31 (working tree). Headless **105/105**; `src/test/smoke_crowd.gd` verifies rubbernecking and panic clearing. A 16-NPC entrance measured ~1.2 ms/frame (824 fps, vsync off), so no LOD/culling was added at the current cap.
|
Completed 2026-10-31 (working tree). Headless **105/105**; `src/test/smoke_crowd.gd` verifies rubbernecking and panic clearing. A 16-NPC entrance measured ~1.2 ms/frame (824 fps, vsync off), so no LOD/culling was added at the current cap.
|
||||||
|
|
||||||
|
|
||||||
`CrowdSystem` is a mood/attention float. PLAN wants people watching, gathering, and panicking.
|
`CrowdSystem` is a mood/attention float. PLAN wants people watching, gathering, and panicking.
|
||||||
|
|
||||||
- [x] On `aggressive_behavior` / `emergency_responder`, nearby idle NPCs stop and face the
|
- [x] On `aggressive_behavior` / `emergency_responder`, nearby idle NPCs stop and face the
|
||||||
@@ -478,7 +473,6 @@ Completed 2026-10-31 (working tree). Headless **105/105**; `src/test/smoke_crowd
|
|||||||
|
|
||||||
Completed 2026-10-31 (working tree). Headless **113/113**; `src/test/smoke_emergency.gd` covers the unjustified-call investigation, the evacuation aftermath, and the review copy.
|
Completed 2026-10-31 (working tree). Headless **113/113**; `src/test/smoke_emergency.gd` covers the unjustified-call investigation, the evacuation aftermath, and the review copy.
|
||||||
|
|
||||||
|
|
||||||
Responder outcomes (`command` / `restraint` / `evacuation` / `arrest`) are log lines. PLAN also
|
Responder outcomes (`command` / `restraint` / `evacuation` / `arrest`) are log lines. PLAN also
|
||||||
wants evacuation, a closed street, and an investigation when the call was unjustified.
|
wants evacuation, a closed street, and an investigation when the call was unjustified.
|
||||||
|
|
||||||
@@ -486,3 +480,401 @@ wants evacuation, a closed street, and an investigation when the call was unjust
|
|||||||
- [x] Unjustified emergency (low danger, no weapon) writes an investigation note on the shift
|
- [x] Unjustified emergency (low danger, no weapon) writes an investigation note on the shift
|
||||||
review and applies the existing false-report / reputation hit.
|
review and applies the existing false-report / reputation hit.
|
||||||
- [x] Review copy names the responder outcome, not only battery and crowd %.
|
- [x] Review copy names the responder outcome, not only battery and crowd %.
|
||||||
|
|
||||||
|
## Body-camera replay
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **120/120**; `src/test/smoke_timeline.gd` opens the timeline from the review, steps through every event, and returns to the review.
|
||||||
|
|
||||||
|
- [x] Shift review: a timeline of recorded incident events (ADR-0005 already stores them).
|
||||||
|
Scrub or step through; no live 3D replay required in the first version.
|
||||||
|
|
||||||
|
## NPC theater
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **127/127**; verified visually with a staged sit/photo/perform lineup.
|
||||||
|
|
||||||
|
Intents `sit` / `photo` / `film` / `perform` already pick waypoints. ADR-0010 rig (face/hair)
|
||||||
|
is in the working tree. Bodies still idle-stand at those spots.
|
||||||
|
|
||||||
|
- [x] Sit pose at `steps_end`. Photo: brief raise of the influencer/tourist prop. Performer
|
||||||
|
stays put and plays the hat/sign.
|
||||||
|
|
||||||
|
## Audio
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **134/134**; `src/test/smoke_audio.gd` checks the bed follows mood and the radio crackles, headless and windowed (real audio server).
|
||||||
|
|
||||||
|
`AudioFeedback` is sine beeps. DONE called that "crowd ambience"; it is not.
|
||||||
|
|
||||||
|
- [x] Loop a low crowd bed that rises with `CrowdSystem.mood`.
|
||||||
|
- [x] Radio open/close crackle on the existing radio panel, still headless-safe (no-op without
|
||||||
|
an audio server).
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **139/139**; verified visually at 1.4x font scale with the shape meter showing.
|
||||||
|
|
||||||
|
- [x] Escalation colors that still read in grayscale (shape or label, not hue alone).
|
||||||
|
- [x] HUD font scale in settings.
|
||||||
|
|
||||||
|
## Scenario content still missing vs PLAN lists
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **149/149**; `src/test/smoke_scenario_content.gd` spawns the refusal event and the celebrity arrival through the real signal path.
|
||||||
|
|
||||||
|
- [x] Joke Shop: guest on the reservation list without ID; borrowed/mismatched ID; guest
|
||||||
|
refuses wand screening; late arrival during a performance.
|
||||||
|
- [x] Studio: filming-boundary repeat (influencer ignores a posted line); celebrity-arrival
|
||||||
|
crowd spike (more rubberneckers, not only one `arrive` spawn).
|
||||||
|
- [x] Building: "activate body camera" and "request reason" once those verbs exist, used in
|
||||||
|
at least one authored event.
|
||||||
|
|
||||||
|
## Distribution follow-up (host config)
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **153/153**.
|
||||||
|
|
||||||
|
Verified via API+host credentials (2026-10-31):
|
||||||
|
|
||||||
|
- [x] Repo is **public** (`private:false`) and account has admin rights.
|
||||||
|
- [x] Release `v0.2.1` now carries all five artifacts (portable binaries + NSIS installer +
|
||||||
|
AppImage).
|
||||||
|
- [x] **Host setting applied**: `[service] REQUIRE_SIGNIN_VIEW = false` in `/etc/gitea/app.ini`
|
||||||
|
(backup written alongside), Gitea restarted. Verified anonymously: repo page 200, API 200,
|
||||||
|
and a release asset downloads with the full 50.8 MB AppImage.
|
||||||
|
|
||||||
|
## Release pipeline follow-up
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **153/153**.
|
||||||
|
|
||||||
|
- [x] Fix `tools/publish_release.sh`: the `~/.git-credentials` branch never set `AUTH`,
|
||||||
|
so every tag build since v0.2.2 failed to publish.
|
||||||
|
- [x] Add a `github.token` fallback in `.gitea/workflows/build.yml` (the repo has no
|
||||||
|
`GITEA_TOKEN` secret).
|
||||||
|
- [x] Fall back to the checkout's git extraheader when no token/credentials exist (the
|
||||||
|
Gitea runner does not expose `github.token`).
|
||||||
|
- [x] Uploads now fail loudly and the release is verified to carry every artifact
|
||||||
|
(a silent HTTP 4xx used to leave an empty release with a green job).
|
||||||
|
- [x] Host disk expanded to 16G; v0.2.0 and v0.2.9 releases restored from the local artifacts.
|
||||||
|
- [x] Host maintenance via `root@192.168.88.30`: apt cache cleaned and journald capped
|
||||||
|
(`SystemMaxUse=300M` in `/etc/systemd/journald.conf.d/99-size.conf`); ~7.6G free.
|
||||||
|
- [x] Verified end to end: tagging v0.2.12 produced a release with all five artifacts
|
||||||
|
(AppImage, Linux, macOS, NSIS installer, Windows) from CI, with no repo secret.
|
||||||
|
- [x] Backfill decision: v0.2.4–v0.2.8 stay superseded; their tags and source remain, and the
|
||||||
|
release page points at the current build. No rebuilds planned.
|
||||||
|
|
||||||
|
## Open Polish
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **153/153**.
|
||||||
|
|
||||||
|
- [x] Keep `%` placeholders stable in `Strings.fmt` when adding keys (positional formatting).
|
||||||
|
|
||||||
|
## Screen design references (QA tooling)
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). 30 Python tooling tests pass; the live judge run classed 3 captures as match and 3 as review (concept mockups are more stylised), with no confident mismatch.
|
||||||
|
|
||||||
|
- [x] `tools/screen_designs.py generate` writes one reference design per screen
|
||||||
|
(from the `docs/` descriptions) to `docs/screens/designs/`, with an offline
|
||||||
|
`--draft` wireframe mode and an OpenRouter `--ai` mockup mode.
|
||||||
|
- [x] `tools/screen_designs.py compare` checks `docs/screenshots/*.png` against the
|
||||||
|
designs (aspect ratio, size, blank-frame, and layout/palette/detail drift).
|
||||||
|
- [x] Vision-model judge for semantic screenshot comparison: `judge` sends the
|
||||||
|
capture + reference design to a vision model, classes verdicts as
|
||||||
|
match/review/diff with a confidence threshold, supports `--dry-run`, `--json`,
|
||||||
|
`--only`, and `--model`, and reads the repo `.env` for the API key.
|
||||||
|
|
||||||
|
## Tooling (when an editor workflow exists)
|
||||||
|
|
||||||
|
Completed 2026-10-31 (working tree). Headless **157/157**; the exported `.tres` venues round-trip through the resource loader.
|
||||||
|
|
||||||
|
- [x] Author venues as `.tres` resources: `tools/export_venues.gd` writes
|
||||||
|
`content/venues/<id>.tres` from the factory, and a test loads them back to check
|
||||||
|
nothing is lost (events, profiles, policy, phases, door lines, locations).
|
||||||
|
- [x] GUT decision: **not adopted.** The custom headless runner (`src/test/test_runner.gd`)
|
||||||
|
already covers 157 assertions without an editor plugin, runs in CI, and keeps the
|
||||||
|
project dependency-free; adding GUT would duplicate coverage and require the editor.
|
||||||
|
|
||||||
|
## In-Game Scene Examples
|
||||||
|
|
||||||
|
Completed 2026-10-31 (v0.3.0 working tree). `python3 tools/screen_designs.py list` reports
|
||||||
|
19 screens (8 scene, 11 UI); the Python tooling tests pass.
|
||||||
|
|
||||||
|
- [x] Extend the `tools/screen_designs.py` catalogue with the screens that exist in code but
|
||||||
|
had no reference: wand screening, Joke Shop door queue, TV Studio celebrity arrival,
|
||||||
|
crowd panic, responder arrival, body-camera timeline, escalation HUD.
|
||||||
|
- [x] Add a `list` subcommand so the catalogue is inspectable without generating images.
|
||||||
|
- [x] Cover the new entries with `tools/test_screen_designs.py` assertions.
|
||||||
|
- [x] Regenerate the deterministic draft references under `docs/screens/designs/`.
|
||||||
|
(`generate` now keeps existing references unless `--force` is passed, so a draft run
|
||||||
|
cannot replace an AI mockup; `--only` selects a subset.)
|
||||||
|
|
||||||
|
## Rendering Quality
|
||||||
|
|
||||||
|
Completed 2026-10-31 (v0.3.0 working tree). Screenshots regenerated; `screen_designs.py
|
||||||
|
compare` reports 6 good / 0 bad.
|
||||||
|
|
||||||
|
- [x] Add a shared surface-material module (`src/game/materials.gd`) with
|
||||||
|
roughness/metallic/uv-scale profiles per surface and a single environment tune-up
|
||||||
|
(tonemap, glow, fog, shadows).
|
||||||
|
- [x] Rebuild `WorldBuilder` surfaces with the material profiles and add street detail:
|
||||||
|
window bands, door trim, bollards, bench, bin, curb, planter foliage.
|
||||||
|
- [x] Per-phase environment values (fog, sky energy, glow) so night reads darker and the
|
||||||
|
marquee glows.
|
||||||
|
- [x] Improve NPC silhouette and materials: per-role clothing palette, caps/hoods/backpacks,
|
||||||
|
sleeves and hands, walking leg swing.
|
||||||
|
- [x] Cover materials/environment/avatar helpers with headless tests
|
||||||
|
(`test_materials.gd`, `test_target_cycle.gd`).
|
||||||
|
|
||||||
|
## AI 3D Model Tooling
|
||||||
|
|
||||||
|
Completed 2026-10-31 (v0.3.0 working tree). `model_pipeline.py build` + `audit` pass for all
|
||||||
|
six props; `test_generated_props.gd` proves they import as scenes and that the world places
|
||||||
|
them.
|
||||||
|
|
||||||
|
- [x] Add `tools/model_pipeline.py`: prop catalogue, deterministic low-poly GLB builder,
|
||||||
|
OpenRouter concept-image generation, palette sampling and GLB audit.
|
||||||
|
- [x] Add `tools/test_model_pipeline.py` (valid GLB structure, deterministic output, audit
|
||||||
|
failures, sRGB→linear conversion, role assignment).
|
||||||
|
- [x] Generate the prop models into `assets/models/generated/` and import them.
|
||||||
|
- [x] Load the generated props in `WorldBuilder` with a procedural fallback when the GLB is
|
||||||
|
missing.
|
||||||
|
|
||||||
|
## Controls, Movement and NPC Selection
|
||||||
|
|
||||||
|
Completed 2026-10-31 (v0.3.0 working tree). The windowed smoke now covers wheel cycling and
|
||||||
|
right-click clearing; the headless suite passes 177/177.
|
||||||
|
|
||||||
|
- [x] Record the re-evaluated control model as ADR-0011.
|
||||||
|
- [x] Rank focus candidates by view angle then distance (`NpcFocus.rank_indices`) instead of
|
||||||
|
insertion order.
|
||||||
|
- [x] Add mouse-wheel target cycling next to `Tab`, right-click clearing focus, and a `Shift`
|
||||||
|
precision walk speed.
|
||||||
|
- [x] Show the nearby-candidate count and the cycle hint in the HUD.
|
||||||
|
- [x] Cover ranking, cycling and walk speed with tests, then re-run the windowed smoke.
|
||||||
|
|
||||||
|
## Web Export Build Target
|
||||||
|
|
||||||
|
Completed 2026-10-31 (v0.3.0). Headless **181/181**; `godot --headless --export-release "Web"`
|
||||||
|
produces a 63 MB static bundle and it serves locally with HTTP 200s for html/wasm/pck.
|
||||||
|
|
||||||
|
- [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/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
|
||||||
|
`src/test/tests/test_web_export.gd`.
|
||||||
|
- [x] Document the artifact in `docs/RELEASING.md`, the README gallery/status, and `PLAN.md`.
|
||||||
|
|
||||||
|
## Prop Texture Atlas
|
||||||
|
|
||||||
|
Completed 2026-10-31 (v0.3.1). Headless **182/182**, Python tooling **53/53**; screenshots
|
||||||
|
regenerated and `screen_designs.py compare` still reports 6 good / 0 bad.
|
||||||
|
|
||||||
|
- [x] `model_pipeline.atlas_for()` packs each prop's palette roles into one small power-of-two
|
||||||
|
PNG and a matching metallic/roughness atlas (roughness in G, metallic in B), with nearest
|
||||||
|
sampling so a palette cell stays a flat colour.
|
||||||
|
- [x] `build_glb()` merges every part into a single primitive with per-vertex atlas UVs and one
|
||||||
|
material (`baseColorTexture` + `metallicRoughnessTexture`), so a prop is one draw material
|
||||||
|
instead of one per part.
|
||||||
|
- [x] `audit_glb()` checks the single-material/atlas contract (one material, both textures,
|
||||||
|
TEXCOORD_0 on every primitive, two images).
|
||||||
|
- [x] Tests: atlas determinism and power-of-two sizing, UV range, palette override reaching the
|
||||||
|
atlas, and a Godot test that each imported prop scene uses exactly one material.
|
||||||
|
- [x] Removed the now-unused sRGB→linear factor helpers (colours travel in the sRGB texture).
|
||||||
|
|
||||||
|
## Complete AI Mockup Set
|
||||||
|
|
||||||
|
Completed 2026-10-31 (v0.3.2). All 19 catalogue screens now have an AI reference; Python
|
||||||
|
tooling **53/53**.
|
||||||
|
|
||||||
|
- [x] Generated mockups for the seven entries that only had wireframes: wand screening,
|
||||||
|
Joke Shop queue, TV Studio arrival, crowd panic, responder arrival, body-camera
|
||||||
|
timeline, escalation HUD.
|
||||||
|
- [x] `generate` loads `.env` like `judge` already did, so `--ai` works without exporting
|
||||||
|
`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.
|
||||||
@@ -4,6 +4,13 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
|
|
||||||
|
- 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.
|
- 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.
|
- 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`.
|
- 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,19 +20,211 @@ 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.
|
- 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.
|
- 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`.
|
||||||
|
|
||||||
## Handoff (read this first)
|
## Handoff (read this first)
|
||||||
|
|
||||||
- Repo: `/run/media/system/DATA/Documents/06-games/security-game`. Godot: `~/.local/bin/godot` (4.3.stable).
|
- **Visual-alignment pass planned (2026-11-01):** `TODO.md` gains a "Visual
|
||||||
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **88/88**.
|
alignment with rendered mockups" section. Goal: restructure generated-image
|
||||||
|
storage, move the reference mockups out of `docs/screens/designs/` into a
|
||||||
|
runtime-accessible root (e.g. `assets/images/`), load the proposed UI/scene
|
||||||
|
art at runtime (start menu, selection screens, HUD, panels), and make the game
|
||||||
|
match the mockups — verified by `screen_designs.py compare` + `judge`. Not
|
||||||
|
started; no code moved yet.
|
||||||
|
|
||||||
|
- **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**.
|
||||||
|
Python tooling tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs test_model_pipeline` → **53**.
|
||||||
Session shell smoke: `godot --headless --path . --script res://src/test/smoke_session_shell.gd` → exit 0.
|
Session shell smoke: `godot --headless --path . --script res://src/test/smoke_session_shell.gd` → exit 0.
|
||||||
Windowed control smoke: `godot --path . --script res://src/test/smoke_focus_radial.gd` → exit 0.
|
Windowed control smoke: `godot --path . --script res://src/test/smoke_focus_radial.gd` → exit 0
|
||||||
|
(now also covers wheel cycling and right-click clearing).
|
||||||
- `main` is in sync with origin. **v0.2.2** tags the session-shell + world-follow-up commit;
|
- `main` is in sync with origin. **v0.2.2** tags the session-shell + world-follow-up commit;
|
||||||
CI builds the portable binaries, NSIS installer, and AppImage on the tag push.
|
CI builds the portable binaries, NSIS installer, and AppImage on the tag push.
|
||||||
- Next work: `TODO.md` body-camera replay timeline, then NPC theater poses (sit/photo/perform)
|
- **v0.3.0 (2026-10-31, working tree):** rendering pass (`src/game/materials.gd`, phase-driven
|
||||||
and audio.
|
environment), generated `.glb` props (`tools/model_pipeline.py`, `assets/models/generated/`),
|
||||||
|
aim-free selection with wheel cycling and Shift precision walk (ADR-0011/0012), and a **Web**
|
||||||
|
export target. `export_presets.cfg` gains a `Web` preset with `variant/thread_support=false`
|
||||||
|
(static hosting, no COOP/COEP); `tools/build_releases.sh` exports `build/web/` and zips it to
|
||||||
|
`build/gig-line-web-<ver>.zip`, and `publish_release.sh` uploads it. Covered by
|
||||||
|
`src/test/tests/test_web_export.gd`.
|
||||||
|
- **Prop atlas (v0.3.1):** `model_pipeline.atlas_for()` builds a per-prop power-of-two PNG with
|
||||||
|
one 8×8 cell per palette role, plus a metallic/roughness atlas (G=roughness, B=metallic). The
|
||||||
|
GLB merges all parts into one primitive with constant per-vertex UVs (cell centre) and a single
|
||||||
|
material (`baseColorTexture` + `metallicRoughnessTexture`, NEAREST sampling). Colours are sRGB in
|
||||||
|
the texture, so the old `srgb_to_linear`/`_linear_rgba` helpers were removed. `audit_glb` fails
|
||||||
|
if a prop is not exactly one material or misses TEXCOORD_0/images.
|
||||||
|
- **Mockups complete (v0.3.2):** `docs/screens/designs/` holds AI references for all 19
|
||||||
|
catalogue screens. Only the six staged captures (01–06) are compared/judged; the other 13
|
||||||
|
have no `capture`, so adding staged captures for the wand panel, timeline and escalation HUD
|
||||||
|
would extend automated visual checks. `generate` now calls `_load_env_file()`.
|
||||||
|
- **Staged captures (v0.3.3):** `capture_screenshots.gd` now emits nine shots. States 7–10 switch
|
||||||
|
to The Joke Shop at elapsed 620 s (EVENING has routine spawns; LATE has none), retry the wand
|
||||||
|
until it alerts, then jump to 760 s after staging for night lighting. The timeline needs
|
||||||
|
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents. The escalation
|
||||||
|
HUD mockup is third-person now; `judge` still classes it `review` (no diff).
|
||||||
|
- **Prop collision (v0.4.0):** `_add_prop_model()` fits a `StaticBody3D` from the model bounds
|
||||||
|
(cylinder for bollard/bin/hydrant, box otherwise) and adds a `NavigationObstacle3D`; NPC agents
|
||||||
|
enable avoidance and move with the safe velocity from `velocity_computed`. The navmesh stays a
|
||||||
|
flat plane, so avoidance (not a baked mesh) routes guests around props. Placements live in
|
||||||
|
`PROP_PLACEMENTS` with `placement_problems()` validation; variants come from `variant_for(index)`.
|
||||||
|
`smoke_props.gd` must be windowed because the guard reads captured-mouse input.
|
||||||
|
- **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.
|
- Do not re-open `override_access`. Do not bind wand to `W`. Do not remove the radio easter egg.
|
||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
|
- **v0.3.0 pass (2026-10-31, uncommitted):** rendering, generated prop models, screen
|
||||||
|
catalogue, aim-free selection. See ADR-0011 (selection) and ADR-0012 (materials/props).
|
||||||
|
- `src/game/materials.gd` (`SurfaceMaterials`) is now the single source of surface
|
||||||
|
recipes + `configure_environment()`. `GameManager.PHASE_LIGHTING` gained
|
||||||
|
`exposure/glow/fog_color/fog_density/fog_energy/contrast/saturation/ambient_energy`.
|
||||||
|
Renderer is **GL Compatibility**, so SSAO/SSIL are unavailable; glow+fog+ACES are used
|
||||||
|
instead and unsupported settings are ignored rather than gated in code.
|
||||||
|
- World: scaled brick (uv 22,10), framed windows with per-window night emission
|
||||||
|
(`i % 3 != 2` lit), stone base course, door trim/handles, curb, lane dashes (the old
|
||||||
|
loop had `Vector3(i * 0.0, …)` and stacked every dash at x=0), street lamps use the
|
||||||
|
metal profile. The two flat planters that clipped the steps were removed.
|
||||||
|
- `tools/model_pipeline.py` builds `assets/models/generated/*.glb` (bollard, bench,
|
||||||
|
trash_bin, planter_box, newspaper_box, fire_hydrant) from box/cylinder specs;
|
||||||
|
`WorldBuilder._add_prop_model()` loads them with a procedural fallback and records
|
||||||
|
`loaded_models` / `fallback_props`. **Gotcha:** glTF `baseColorFactor` is LINEAR —
|
||||||
|
writing sRGB made every prop pastel; `srgb_to_linear()` is applied in `build_glb`.
|
||||||
|
**Gotcha:** a new/changed `.glb` needs `godot --headless --path . --import` before
|
||||||
|
`ResourceLoader.exists()` / `load()` sees it (test_generated_props fails otherwise).
|
||||||
|
- `model_pipeline.py concept` (OpenRouter, reads repo `.env`) writes per-prop concept
|
||||||
|
sheets to `assets/models/concepts/`; `dominant_colors()` centre-crops and weights by
|
||||||
|
saturation (a grey studio backdrop otherwise wins), `assign_roles()` maps colours by
|
||||||
|
hue/value. Delete a concept file to fall back to the catalogue palette.
|
||||||
|
- NPC avatars: `outfit_for(type, seed)` (pure) gives shirt/trousers/hair/skin +
|
||||||
|
accessory (cap, hood, backpack, sunglasses, lanyard) and height scale; `_arms` and
|
||||||
|
`_legs` now swing (`limb_swing`) while walking. Keep `Rig/Head` named for focus.
|
||||||
|
- Selection: `NpcFocus.rank_indices` (angle then distance) — `pick_index` = first.
|
||||||
|
`GuardController.cycle_target(direction)` walks nearest-first `nearby_candidates()` and
|
||||||
|
sets `target_pinned`, which makes `_update_target()` skip the cone. `clear_target()`
|
||||||
|
sets `focus_suppress_s = 0.75` so the cone does not instantly re-acquire the person.
|
||||||
|
New input actions `walk_slow` (Shift), `cycle_target_next` / `cycle_target_prev`
|
||||||
|
(wheel down / up). Right click with nothing open clears focus (Esc still pauses).
|
||||||
|
- HUD: `nearby_label` + `set_nearby_hint(count)` show `hud.nearby`; `hud.controls` and
|
||||||
|
`hud.targeting` mention the wheel and Shift.
|
||||||
|
- `tools/screen_designs.py`: catalogue now 19 screens (added wand_screening,
|
||||||
|
joke_shop_queue, studio_arrival, crowd_panic, responder_arrival,
|
||||||
|
body_camera_timeline, escalation_hud), new `list` command, `generate --only/--force`
|
||||||
|
(existing designs are kept by default so a draft run cannot clobber AI mockups),
|
||||||
|
and `judge --dry-run` no longer raises `KeyError: 'class'`.
|
||||||
|
- Screenshot tool leak: `capture_screenshots.gd` intermittently prints
|
||||||
|
`ObjectDB instances leaked at exit`. Verified against a `git worktree` of HEAD~ — the
|
||||||
|
baseline leaks more often (3/3 vs 1/3), so it is a pre-existing quit-timing artifact,
|
||||||
|
not a regression. Normal boots are clean.
|
||||||
|
|
||||||
- Access artifacts: `src/core/access_loadout.gd` builds from event `override_access` + venue policy.
|
- Access artifacts: `src/core/access_loadout.gd` builds from event `override_access` + venue policy.
|
||||||
Spawn signal is now `(profile, intent, npc_id, event)`. Recognition is not auto-attached to every
|
Spawn signal is now `(profile, intent, npc_id, event)`. Recognition is not auto-attached to every
|
||||||
guest. `AccessVerifier.verify(..., already_screened=false)` so Joke Shop scoring uses
|
guest. `AccessVerifier.verify(..., already_screened=false)` so Joke Shop scoring uses
|
||||||
@@ -35,7 +234,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
`hud.controls` / `hud.screen_first` updated.
|
`hud.controls` / `hud.screen_first` updated.
|
||||||
- Control pass (in tree, uncommitted, smoke-tested): cone focus via `src/game/npc_focus.gd`,
|
- Control pass (in tree, uncommitted, smoke-tested): cone focus via `src/game/npc_focus.gd`,
|
||||||
accel/decel, gravity only airborne, `input_locked` freeze, Esc no longer on the guard.
|
accel/decel, gravity only airborne, `input_locked` freeze, Esc no longer on the guard.
|
||||||
`NpcAvatar` in group `npc`; neck/hair/eyes/shoes; torus head ring + `Label3D` name; `set_focus_visual`.
|
`NpcAvatar` in group `npc`; neck/hair/eyes/shoes; blue focus circle + `Label3D` name; `set_focus_visual`.
|
||||||
`src/game/ui/action_radial.gd` doughnut (LMB/Space open, A/D/arrows, Space/click confirm, Esc/RMB
|
`src/game/ui/action_radial.gd` doughnut (LMB/Space open, A/D/arrows, Space/click confirm, Esc/RMB
|
||||||
cancel). Crosshair CanvasLayer. Emergency still `R` only. Number keys 1–6 when the wheel is closed.
|
cancel). Crosshair CanvasLayer. Emergency still `R` only. Number keys 1–6 when the wheel is closed.
|
||||||
- Named input actions now exist and are used: `action_menu`, `confirm`, `cancel` (Esc + RMB),
|
- Named input actions now exist and are used: `action_menu`, `confirm`, `cancel` (Esc + RMB),
|
||||||
@@ -58,7 +257,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
(`assets/textures/skyline.png`, regenerated RGBA with a transparent sky) and an
|
(`assets/textures/skyline.png`, regenerated RGBA with a transparent sky) and an
|
||||||
`TRANSPARENCY_ALPHA_SCISSOR` unshaded material (`_skyline_mat`); steps + landing are a
|
`TRANSPARENCY_ALPHA_SCISSOR` unshaded material (`_skyline_mat`); steps + landing are a
|
||||||
`StaticBody3D` with per-tread `BoxShape3D` colliders; marquee `font_size` 160 / outline 24; focus
|
`StaticBody3D` with per-tread `BoxShape3D` colliders; marquee `font_size` 160 / outline 24; focus
|
||||||
torus 0.26–0.34 + name tag 48px; `NpcAvatar.ephemeral`/`lifetime_s` despawns routine traffic
|
circle (blue disc in front of the face) + name tag 48px; `NpcAvatar.ephemeral`/`lifetime_s` despawns routine traffic
|
||||||
(authored events stay). Guard spawn height verified on the landing (guard_y=0.84).
|
(authored events stay). Guard spawn height verified on the landing (guard_y=0.84).
|
||||||
- **PLAN verbs (2026-10-31, uncommitted):** the radial now has 12 slices
|
- **PLAN verbs (2026-10-31, uncommitted):** the radial now has 12 slices
|
||||||
(`Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio`).
|
(`Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio`).
|
||||||
@@ -94,6 +293,52 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
`_begin_incident()` now starts one per focused NPC (`has_open_incident()` guard), verbs call
|
`_begin_incident()` now starts one per focused NPC (`has_open_incident()` guard), verbs call
|
||||||
`_ensure_incident()`, and `ShiftCoordinator._finish_shift()` closes leftovers as `unresolved`
|
`_ensure_incident()`, and `ShiftCoordinator._finish_shift()` closes leftovers as `unresolved`
|
||||||
before building the review.
|
before building the review.
|
||||||
|
- **Editor-ready venues (2026-10-31):** `tools/export_venues.gd` (a SceneTree script) saves the
|
||||||
|
three factory scenarios to `content/venues/<id>.tres` with `ResourceSaver`; the `.tres` files
|
||||||
|
round-trip through `ResourceLoader` (events, profiles, policy, shift phases, door lines,
|
||||||
|
locations). `test_venue_resources.gd` also fails if the exported files are missing or stale
|
||||||
|
relative to the factory, so re-run the export after changing `ScenarioFactory`.
|
||||||
|
- **Vision judge (2026-10-31):** `python3 tools/screen_designs.py judge` sends the capture plus
|
||||||
|
its concept mockup to a vision model (`DEFAULT_JUDGE_MODEL = google/gemini-2.5-flash-lite`,
|
||||||
|
override with `--model`; `openai/gpt-4o-mini` also works). Reads the repo `.env` via
|
||||||
|
`_load_env_file()` (never overrides real env). Verdicts are classed `match` / `review`
|
||||||
|
(mismatch below `--fail-confidence`, default 0.6) / `diff` (confident mismatch → exit 1) /
|
||||||
|
`unknown`. `--dry-run`, `--json`, `--only` supported. The prompt explicitly ignores art style,
|
||||||
|
because the references are stylised mockups; the live run gave 3 match, 3 review, 0 diff.
|
||||||
|
Python tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs`.
|
||||||
|
- **String formatting safety (2026-10-31):** `Strings.placeholder_count()` counts `%` specs
|
||||||
|
(ignoring `%%`), `fmt()` warns and returns the template when the arg count does not match
|
||||||
|
(instead of raising an engine error), and `validate()` lints the table for trailing `%`,
|
||||||
|
unsupported conversions, and mixed positional/sequential placeholders. Tests cover all three
|
||||||
|
plus a formatting pass over every placeholder key with type-correct dummy args.
|
||||||
|
- **Authored scenario content (2026-10-31):** events can now carry `hint` (a string key logged
|
||||||
|
when the avatar spawns), `extra_spawns` (celebrity arrivals pull in tourists/influencers via
|
||||||
|
`_spawn_npc` with `kind: routine`), and `refuses_screening` (copied to
|
||||||
|
`NPCController.refuses_screening`; `_toggle_wand_screening` logs `wand.refused`, records a
|
||||||
|
`wand_refused` event, applies a small prop hit, and does not open the panel). `film_boundary`
|
||||||
|
is a new intent that maps to `steps_end` so the influencer stands past the posted line.
|
||||||
|
- **Accessibility (2026-10-31):** escalation text is `"<name> [###--] L3/5"` via
|
||||||
|
`Hud.escalation_meter(level)` (pure, tested) so the cue survives grayscale. HUD font scale uses
|
||||||
|
`add_theme_font_size_override` on the core labels (`set_font_scale`), **not** `Control.scale`,
|
||||||
|
which pushed the right-anchored clock label off-screen. Settings load before `_build_ui()`, so
|
||||||
|
`_ready()` re-applies player settings after the HUD exists.
|
||||||
|
- **Audio (2026-10-31):** `AudioFeedback.start_crowd_bed()` builds a 4 s looping `AudioStreamWAV`
|
||||||
|
(one-pole-filtered noise + 68/103 Hz drone, seeded) and `set_crowd_level(mood)` maps 0..1 to
|
||||||
|
-34..-12 dB. `play_radio_open/close` push short filtered-noise bursts. All procedural; with a
|
||||||
|
dummy (headless) audio server the calls are no-ops. `_free_player()` returns null when `_ready`
|
||||||
|
has not run (tests), and callers handle null.
|
||||||
|
- **NPC theater (2026-10-31):** `NpcAvatar.theater_pose()` maps `intent` + `arrived` to
|
||||||
|
idle/sit/photo/film/perform. Pure helpers `crouch_for_pose`, `prop_lift_for_pose`,
|
||||||
|
`sway_for_pose` keep the math testable. Sit lowers the rig by 0.34 and swings the legs forward
|
||||||
|
(negative X pitch; positive looked like kneeling). `_photo_prop` is set for influencer/tourist
|
||||||
|
cameras, `_sign_prop` for protester/performer boards; their base Y values are captured at build
|
||||||
|
time so the animation offsets from rest.
|
||||||
|
- **Body-camera timeline (2026-10-31):** `src/game/ui/event_timeline.gd` flattens
|
||||||
|
`review.incidents.incidents[].events` into one time-ordered list (`flatten`), formats each row
|
||||||
|
with `describe`, and steps with Left/Right or the buttons. `ShiftCoordinator.started_at_ms`
|
||||||
|
gives the review an absolute start for mm:ss stamps. `GameManager._open_timeline` hides the
|
||||||
|
review dim (two stacked overlays made the screen nearly black) and `_timeline_from_review`
|
||||||
|
restores it on close. Smoke: `src/test/smoke_timeline.gd`.
|
||||||
- **Radio dispatch was dead after the first shift:** `coordinator.setup()` builds a new
|
- **Radio dispatch was dead after the first shift:** `coordinator.setup()` builds a new
|
||||||
`RadioSystem`, so the `responder_arrived` connection made in `_ready` was lost on every venue
|
`RadioSystem`, so the `responder_arrived` connection made in `_ready` was lost on every venue
|
||||||
switch/restart (responders never resolved anything). `_connect_radio()` now re-wires it next to
|
switch/restart (responders never resolved anything). `_connect_radio()` now re-wires it next to
|
||||||
@@ -112,12 +357,49 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
needs `mipmaps/generate=true`.
|
needs `mipmaps/generate=true`.
|
||||||
- **Asset gotcha:** `assets/textures/game_ready/` is *not* imported (no `.import`). Use the
|
- **Asset gotcha:** `assets/textures/game_ready/` is *not* imported (no `.import`). Use the
|
||||||
imported `assets/textures/skyline.png` path. Untracked duplicate folder should not be referenced.
|
imported `assets/textures/skyline.png` path. Untracked duplicate folder should not be referenced.
|
||||||
- Host sign-in follow-up remains deferred. PLAN.md + ADRs 0007–0010 still describe the intended UX.
|
- Host sign-in follow-up is **done**: `REQUIRE_SIGNIN_VIEW = false` in `/etc/gitea/app.ini`
|
||||||
|
(backup `app.ini.bak-*` alongside), Gitea restarted; anonymous repo page/API return 200 and
|
||||||
|
release assets download in full. Reverse by setting it back to `true` and restarting.
|
||||||
|
- **Release publishing was broken since v0.2.2 (fixed 2026-10-31):** `publish_release.sh`
|
||||||
|
only built `AUTH` in the `GITEA_TOKEN` branch; the `~/.git-credentials` branch set `CRED`
|
||||||
|
and then used unbound `${AUTH[@]}` under `set -u`, so every API call failed and the tag
|
||||||
|
job ended "could not create or find release". The repo also has **no** `GITEA_TOKEN`
|
||||||
|
secret (`/actions/secrets` is empty) and the runner has no git credentials. Fixes: the
|
||||||
|
script sets `AUTH=(-u "$CRED")`; the workflow falls back to `github.token`
|
||||||
|
(`secrets.GITEA_TOKEN || github.token`) — but `github.token` is **not** provided by this
|
||||||
|
Gitea/act runner, so the env dump showed no token and the v0.2.10 tag build failed again.
|
||||||
|
Final fix: `publish_release.sh` falls back to the **local git extraheader** that
|
||||||
|
`actions/checkout` writes (`git config --local --get-regexp '^http\..*\.extraheader$'`),
|
||||||
|
which carries the runner's basic-auth credentials for the same server. Verified locally by
|
||||||
|
adding that extraheader and publishing v0.2.10 (all five artifacts). **v0.2.9 and v0.2.10
|
||||||
|
are published**; v0.2.4–v0.2.8 tags have no artifacts. Verify the CI path on the next tag.
|
||||||
|
- **CI publishing verified (2026-10-31):** tagging v0.2.12 produced a release with all five
|
||||||
|
artifacts automatically (no repo secret), confirming the checkout-extraheader fallback.
|
||||||
|
- **Host access (2026-10-31):** `root@192.168.88.30` now works from this workstation (key auth),
|
||||||
|
host name `gitea`, Gitea 28.2.0. Maintenance applied: `apt-get clean`, journald capped to
|
||||||
|
`SystemMaxUse=300M` (`/etc/systemd/journald.conf.d/99-size.conf`, journal ~133M), disk at
|
||||||
|
~7.6G free of 16G.
|
||||||
|
- **Host disk resolved (2026-10-31):** the Gitea VM root filesystem was expanded from 7.8G to
|
||||||
|
16G (`/dev/mapper/pve-vm--500--disk--0`, now ~7.8G free). The pruned **v0.2.0** and **v0.2.9**
|
||||||
|
releases were restored from the local `build/` artifacts and verified (4 and 5 assets).
|
||||||
|
`git.allucanget.biz` runs on this host as user `zwitschi`; root@192.168.88.30 is **not**
|
||||||
|
reachable from this workstation (publickey denied; password auth closes the banner), so host
|
||||||
|
maintenance must go through a `zwitschi` login with sudo.
|
||||||
|
- **Host disk full (2026-10-31, historical):** the v0.2.11 tag build created release id 27 and logged the
|
||||||
|
uploads, but they failed with `HTTP 500 Attachments.Save: write … no space left on device`
|
||||||
|
(`/dev/mapper/pve-vm--500--disk--0`, 7.8G, 100%, 0 free). `git push` then failed too
|
||||||
|
("unpacker error"). Unblocked by deleting the superseded **v0.2.0** and **v0.2.9** releases
|
||||||
|
via the API (~565 MB of assets), then publishing v0.2.11 locally with the fixed script
|
||||||
|
(verified 5 assets). Remaining ~150 MB is tight — the next release needs host cleanup on
|
||||||
|
192.168.88.30: `sudo journalctl --vacuum-size=100M` (journal 837 MB), `sudo apt-get clean`
|
||||||
|
(91 MB), clear `/var/lib/gitea/data/attachments/tmp`. SSH works as `zwitschi`; `sudo -n`
|
||||||
|
needs a password. **Releases now:** v0.2.11, v0.2.10, v0.2.1 (5 assets each); v0.2.0 and
|
||||||
|
v0.2.9 entries were pruned. PLAN.md + ADRs 0007–0010 still describe the intended UX.
|
||||||
- **Release v0.2.1 is LIVE** (2026-10-31): CI run 1110 built the NSIS installer + AppImage
|
- **Release v0.2.1 is LIVE** (2026-10-31): CI run 1110 built the NSIS installer + AppImage
|
||||||
and published — installers/AppImage are on the releases page. Releasing = bump version → tag → push.
|
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
|
- 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
|
(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
|
classic BMP entries (ImageMagick `icon:auto-resize=48,32,16`; PIL emits PNG-compressed ICOs NSIS
|
||||||
cannot load).
|
cannot load).
|
||||||
- appimagetool runs system `appstreamcli` if present (strict, exit 3 on warnings) — project
|
- appimagetool runs system `appstreamcli` if present (strict, exit 3 on warnings) — project
|
||||||
@@ -204,8 +486,10 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
- Screen-design references (`tools/screen_designs.py`, tests in `tools/test_screen_designs.py`):
|
- Screen-design references (`tools/screen_designs.py`, tests in `tools/test_screen_designs.py`):
|
||||||
`generate` turns the screen descriptions in `docs/` (README captions, ADRs 0007–0010,
|
`generate` turns the screen descriptions in `docs/` (README captions, ADRs 0007–0010,
|
||||||
PLAN, architecture) into reference images under `docs/screens/designs/` — `--draft`
|
PLAN, architecture) into reference images under `docs/screens/designs/` — `--draft`
|
||||||
draws offline Pillow wireframes, `--ai` generates OpenRouter mockups (default model
|
draws offline Pillow wireframes, `--ai` generates realistic 3D scenes / in-game UI via
|
||||||
`openai/gpt-5-image-mini`). `compare` checks `docs/screenshots/*.png` for aspect/size/
|
OpenRouter (default model `openai/gpt-5-image-mini`). Screens are tagged `scene` or
|
||||||
|
`ui`; prompts get a shared art-direction suffix, and requests use a 3:2 landscape
|
||||||
|
source cropped to 16:9 (not a square centre-crop). `compare` checks `docs/screenshots/*.png` for aspect/size/
|
||||||
blank-frame and coarse layout/palette/detail drift. Drift distances are warnings, not
|
blank-frame and coarse layout/palette/detail drift. Drift distances are warnings, not
|
||||||
failures: a mockup never matches a real render, so semantic composition is a follow-up
|
failures: a mockup never matches a real render, so semantic composition is a follow-up
|
||||||
vision-model judge (tracked in TODO).
|
vision-model judge (tracked in TODO).
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Game Plan: Step Line
|
# Game Plan: Gig Line
|
||||||
|
|
||||||
## Premise
|
## Premise
|
||||||
|
|
||||||
@@ -399,6 +399,27 @@ The studio also has authorized guests and staff. They must either be recognized
|
|||||||
- Someone presents a counterfeit wristband or credential.
|
- 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.
|
- 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).
|
||||||
|
|
||||||
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.
|
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.
|
Future venues can reuse this structure while changing the rules, equipment, staff roles, and types of access decisions.
|
||||||
@@ -452,13 +473,14 @@ The following commands apply once the Godot project is scaffolded:
|
|||||||
|
|
||||||
## Export Targets
|
## Export Targets
|
||||||
|
|
||||||
Desktop is the first target: Windows, Linux, and macOS. Web export is a later option.
|
Desktop is the first target: Windows, Linux, and macOS. A Web export now ships as well
|
||||||
|
(non-threaded, static hosting); it is published with each release as a zip.
|
||||||
|
|
||||||
## Player Control, Focus, and Action Menu (next pass)
|
## Player Control, Focus, and Action Menu (next pass)
|
||||||
|
|
||||||
The prototype already walks and looks in first person, but the control scheme fights itself and targeting is a hairline. This pass replaces that with three things that belong together: solid locomotion, a visible focus on a person, and a round action menu for what to do with them.
|
The prototype already walks and looks in first person, but the control scheme fights itself and targeting is a hairline. This pass replaces that with three things that belong together: solid locomotion, a visible focus on a person, and a round action menu for what to do with them.
|
||||||
|
|
||||||
Decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md) locomotion and input layers, [ADR-0008](docs/adr/0008-cone-focus-and-head-ring.md) cone focus and head ring, [ADR-0009](docs/adr/0009-radial-action-menu.md) radial action menu, [ADR-0010](docs/adr/0010-procedural-npc-presentation.md) procedural NPC presentation.
|
Decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md) locomotion and input layers, [ADR-0008](docs/adr/0008-cone-focus-and-head-ring.md) cone focus and focus circle, [ADR-0009](docs/adr/0009-radial-action-menu.md) radial action menu, [ADR-0010](docs/adr/0010-procedural-npc-presentation.md) procedural NPC presentation.
|
||||||
|
|
||||||
### What is wrong today
|
### What is wrong today
|
||||||
|
|
||||||
@@ -496,17 +518,17 @@ Replace the hairline ray as the primary picker.
|
|||||||
- `Tab` still cycles people inside 12 m as the aim-free path. Cycling sets the same focused person the cone would.
|
- `Tab` still cycles people inside 12 m as the aim-free path. Cycling sets the same focused person the cone would.
|
||||||
- Clearing: look away until the head leaves the circle for a few frames (small grace so a bob does not drop them), or they walk out of range.
|
- Clearing: look away until the head leaves the circle for a few frames (small grace so a bob does not drop them), or they walk out of range.
|
||||||
|
|
||||||
### Head ring
|
### Focus circle
|
||||||
|
|
||||||
When a person is in focus, draw a thin ring around their head.
|
When a person is in focus, draw a blue, semi-transparent circle in front of their face.
|
||||||
|
|
||||||
- Attach to the avatar `Head` node, sitting just above the skull, billboarded to the camera so it stays a circle on screen.
|
- Attach to the avatar `Head` node, just in front of the face, billboarded to the camera so it stays a circle on screen.
|
||||||
- Slow spin is fine; no ping-pong scale spam.
|
- Slow spin is fine; no ping-pong scale spam.
|
||||||
- Idle focus: pale gold, modest size. Radial open on that person: brighter, slightly larger.
|
- Idle focus: blue, semi-transparent, modest size. Radial open on that person: brighter, slightly larger.
|
||||||
- Optional small name under the ring (`display_name` only, not the archetype cheat `Violent Threat` in the world). The existing HUD target line can keep the archetype for the prototype.
|
- Optional small name under the circle (`display_name` only, not the archetype cheat `Violent Threat` in the world). The existing HUD target line can keep the archetype for the prototype.
|
||||||
- One ring at a time. Everyone else has none.
|
- One circle at a time. Everyone else has none.
|
||||||
|
|
||||||
Implementation sketch: `MeshInstance3D` torus or a thin unshaded ring mesh on the avatar, toggled from `GuardController` / `GameManager` when focus changes. Screen-space fallback (unproject head, draw a `Control` circle) is acceptable if the 3D torus z-fights on hats.
|
Implementation sketch: a thin unshaded `MeshInstance3D` circle (flat disc) in front of the face on the avatar, toggled from `GuardController` / `GameManager` when focus changes. Screen-space fallback (unproject head, draw a `Control` circle) is acceptable if the 3D circle z-fights on hats.
|
||||||
|
|
||||||
### Round action HUD
|
### Round action HUD
|
||||||
|
|
||||||
@@ -545,7 +567,7 @@ A doughnut / pie menu centered on the screen, opened on the focused person.
|
|||||||
**Cancel**
|
**Cancel**
|
||||||
|
|
||||||
- `Esc` or right mouse button: hide the menu, no action, no recorder event, recapture mouse.
|
- `Esc` or right mouse button: hide the menu, no action, no recorder event, recapture mouse.
|
||||||
- Focus and the head ring stay as they were.
|
- Focus and the focus circle stay as they were.
|
||||||
|
|
||||||
Number keys `1`–`6` may remain as shortcuts that skip the wheel so a smoke-test script still works. They must not fire while the wheel is open (the wheel owns `A` / `D` / `Space`). Document both in the README.
|
Number keys `1`–`6` may remain as shortcuts that skip the wheel so a smoke-test script still works. They must not fire while the wheel is open (the wheel owns `A` / `D` / `Space`). Document both in the README.
|
||||||
|
|
||||||
@@ -590,6 +612,18 @@ AI textures in `assets/textures/ai/` stay out of this pass unless a specific map
|
|||||||
- `src/test/tests/test_focus_and_radial.gd` — cone pick, wrap, cancel emits nothing, `W` is not wand.
|
- `src/test/tests/test_focus_and_radial.gd` — cone pick, wrap, cancel emits nothing, `W` is not wand.
|
||||||
- README controls table and smoke checklist.
|
- README controls table and smoke checklist.
|
||||||
|
|
||||||
|
## Rendering, Models, and Selection (v0.3.0 pass)
|
||||||
|
|
||||||
|
Decisions: [ADR-0011](docs/adr/0011-aim-free-selection-and-precision-walk.md) aim-free selection and precision walk, [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md) surface materials and generated props.
|
||||||
|
|
||||||
|
The blockout look and the aim-first selection model were the two biggest gaps left by the control pass. This pass closes them without changing the game's rules.
|
||||||
|
|
||||||
|
**Look.** Surfaces get real profiles instead of stock materials: brick is rough and finely tiled, concrete is rougher still, asphalt is smoother and damp-looking, glass is smooth and slightly metallic. The environment gains ACES tonemapping, soft glow, aerial fog and a mild grade, all driven by the same per-phase configuration that already moves the sun and sky. The facade gains framed windows that light up after dark, a stone base course, door trim and handles. Street furniture becomes real geometry: bollards, benches, bins, planters, newspaper boxes and a hydrant, built as `.glb` props by `tools/model_pipeline.py` with a procedural fallback. NPCs get per-role clothing palettes, small role accessories (cap, hood, backpack, sunglasses, lanyard), hands, shoes, and an arm/leg walk swing. The rule stays: the player judges people from clothes, props and behaviour, never from an archetype label.
|
||||||
|
|
||||||
|
**Models.** Prop meshes are deterministic primitives, so the build works offline. An image model can supply the per-prop colour palette from a concept sheet; that is the only AI influence on geometry, and it is optional. Colours are converted from sRGB to linear before they reach glTF, because glTF factors are linear.
|
||||||
|
|
||||||
|
**Selection.** The focus cone ranks heads by angle from the crosshair then distance. `Tab` and the mouse wheel cycle nearby people nearest-first and pin the choice, so aim-free play works. Right click clears focus and briefly suppresses re-acquisition. Holding `Shift` walks at half speed for close work at the door. Escape keeps its single meaning (close the top panel, otherwise pause).
|
||||||
|
|
||||||
## Design Direction
|
## Design Direction
|
||||||
|
|
||||||
The game should feel like a tense workplace simulation with occasional crisis moments. The most satisfying outcome is not overpowering an NPC; it is recognizing the situation early and resolving it calmly.
|
The game should feel like a tense workplace simulation with occasional crisis moments. The most satisfying outcome is not overpowering an NPC; it is recognizing the situation early and resolving it calmly.
|
||||||
|
|||||||
@@ -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:
|
The central design goal is simple:
|
||||||
|
|
||||||
@@ -13,23 +13,29 @@ The central design goal is simple:
|
|||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|  | |
|
||||||
|
|
||||||
*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
|
## ⬇️ Downloads
|
||||||
|
|
||||||
| Platform | Package |
|
| Platform | Package |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **Windows — installer** | [`step-line-setup-<ver>.exe`](https://git.allucanget.biz/zwitschi/security-game/releases) · contains all game data, shortcuts + uninstaller |
|
| **Windows — installer** | [`gig-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 |
|
| **Windows — portable** | [`gig-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 — AppImage** | [`gig-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 |
|
| **Linux — portable** | [`gig-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 |
|
| **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>
|
**All releases:** <https://git.allucanget.biz/zwitschi/security-game/releases>
|
||||||
|
|
||||||
> **Latest: v0.2.5** — emergency aftermath (evacuation clears the entrance, unjustified calls open
|
> Downloads are public (no sign-in required) since the host's instance-wide
|
||||||
> an investigation, the review names the responder outcome).
|
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
|
||||||
|
>
|
||||||
|
> **Latest: v0.6.0** — Blender-authored character/gear/prop library with baked AO, higher-resolution environment textures, and phase-driven library props.
|
||||||
> Releases are built automatically when a `vX.Y.Z` tag is pushed
|
> Releases are built automatically when a `vX.Y.Z` tag is pushed
|
||||||
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
|
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
|
||||||
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
|
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
|
||||||
@@ -39,7 +45,7 @@ The central design goal is simple:
|
|||||||
> the page and tag URLs are public.
|
> the page and tag URLs are public.
|
||||||
>
|
>
|
||||||
> macOS builds are unsigned: right-click → Open, or clear the quarantine with
|
> 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).
|
> 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
|
If you are here to develop or test locally instead of downloading, jump to
|
||||||
@@ -137,8 +143,8 @@ The architecture maps onto Godot 4 nodes, scenes, and custom resources as descri
|
|||||||
- **Navigation:** `NavigationRegion3D` and `NavigationAgent3D`
|
- **Navigation:** `NavigationRegion3D` and `NavigationAgent3D`
|
||||||
- **NPC behavior:** Custom lightweight finite state machines driven by `NpcProfile` resources
|
- **NPC behavior:** Custom lightweight finite state machines driven by `NpcProfile` resources
|
||||||
- **UI and audio:** Godot `Control` nodes, themes, audio buses, and `AudioStreamPlayer` nodes
|
- **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/`; structural geometry stays procedural (`WorldBuilder`). The legacy `tools/generate_textures.py --ai` batch remains available.
|
- **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.
|
- **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
|
- **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
|
- **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
|
- **Targets:** Windows, Linux, and macOS desktop first; web later
|
||||||
@@ -180,6 +186,124 @@ python3 -m unittest discover -s tools -p 'test_*.py'
|
|||||||
|
|
||||||
The audit classifies measurable fit issues as `good` or `bad` based on role-specific dimensions, aspect ratio, alpha requirements, and tile-edge continuity. It lists perspective/projection as a visual review item because image semantics cannot be judged reliably by these deterministic checks.
|
The audit classifies measurable fit issues as `good` or `bad` based on role-specific dimensions, aspect ratio, alpha requirements, and tile-edge continuity. It lists perspective/projection as a visual review item because image semantics cannot be judged reliably by these deterministic checks.
|
||||||
|
|
||||||
|
### Prop models
|
||||||
|
|
||||||
|
Street furniture (bollards, benches, bins, planters, newspaper boxes, a hydrant)
|
||||||
|
is real 3D geometry generated by `tools/model_pipeline.py` and written to
|
||||||
|
`assets/models/generated/*.glb`. Godot imports each file as a `PackedScene`, and
|
||||||
|
`WorldBuilder` instantiates it with a procedural fallback when the model or its
|
||||||
|
import is missing, so the world still builds on a fresh checkout.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Print the catalogue (parts, triangle count, whether a model/concept exists)
|
||||||
|
python3 tools/model_pipeline.py list
|
||||||
|
|
||||||
|
# Rebuild the models (deterministic, offline) and validate them
|
||||||
|
python3 tools/model_pipeline.py build
|
||||||
|
python3 tools/model_pipeline.py audit
|
||||||
|
|
||||||
|
# Optional: an image model supplies the colour palette per prop
|
||||||
|
# (requires OPENROUTER_API_KEY; the repo .env is read automatically)
|
||||||
|
python3 tools/model_pipeline.py concept
|
||||||
|
python3 tools/model_pipeline.py concept --only bench --force
|
||||||
|
```
|
||||||
|
|
||||||
|
Each prop is a small list of boxes and cylinders, so `build` needs no network and
|
||||||
|
the output is byte-identical between runs. `concept` asks an image model for a
|
||||||
|
studio-style reference; `dominant_colors` and `assign_roles` then map that image's
|
||||||
|
colours onto material roles (darkest → metal, warm saturated → wood, light grey →
|
||||||
|
stone, green → foliage). glTF stores colours in linear space, so the tool converts
|
||||||
|
the sRGB palettes before writing them. Re-run `build` and `godot --headless
|
||||||
|
--path . --import` after changing a prop. See
|
||||||
|
[ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md).
|
||||||
|
|
||||||
|
Surface materials share one definition in `src/game/materials.gd`: named
|
||||||
|
roughness/metallic profiles per surface, plus the environment tune-up (ACES
|
||||||
|
tonemapping, glow, fog, colour grading) that the phase lighting applies.
|
||||||
|
|
||||||
|
### NPC character model
|
||||||
|
|
||||||
|
NPCs are moving from the procedural capsule rig to a skinned, UV-mapped base
|
||||||
|
character authored in Blender ([ADR-0013](docs/adr/0013-blender-authored-npc-characters.md)).
|
||||||
|
`tools/npc_pipeline.py` builds `assets/models/generated/npc_base.glb` plus the
|
||||||
|
per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its
|
||||||
|
five material slots from the seed-driven outfit, and drives the bones from the
|
||||||
|
existing animation code; the procedural rig remains the offline fallback (see
|
||||||
|
the [graphics plan](docs/plan-graphics-v0.6.md)).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Print the frozen contract (slots, bone names, triangle budget)
|
||||||
|
python3 tools/npc_pipeline.py list
|
||||||
|
|
||||||
|
# Rebuild the skinned base mesh + neutral maps in Blender and validate it
|
||||||
|
flatpak run org.blender.Blender --background --python "$PWD/tools/npc_pipeline.py" -- build
|
||||||
|
python3 tools/npc_pipeline.py audit
|
||||||
|
|
||||||
|
# Layer the AI clothing albedos (shirt/trousers) over the procedural weave, and
|
||||||
|
# derive per-fabric outfit colours for the avatar palette
|
||||||
|
python3 tools/npc_pipeline.py prepare
|
||||||
|
python3 tools/npc_pipeline.py palette
|
||||||
|
```
|
||||||
|
|
||||||
|
`prepare` resizes the AI fabric tiles (kept locally in `assets/textures/npc/ai/`,
|
||||||
|
which is gitignored) onto the `cloth`/`pants` albedos; `build` bakes them into the
|
||||||
|
`.glb` and regenerates the procedural normal + packed ORM maps. `palette` writes
|
||||||
|
`assets/textures/npc/palette.json` using `model_pipeline.dominant_colors`.
|
||||||
|
|
||||||
|
### Blender asset library (loaded by the game)
|
||||||
|
|
||||||
|
`tools/blender_assets.py` models a larger library procedurally in Blender for the
|
||||||
|
v0.6.0 integration: **8 skinned NPC characters** (casual, jacket, coat, hi-vis vest,
|
||||||
|
dress, hoodie, firefighter, guard), each a drop-in for `npc_base.glb` (same bones,
|
||||||
|
joint positions and tint slots); **18 gear pieces** authored at head/hand/back/chest
|
||||||
|
attach points (caps, hood, helmet, backpack, SCBA tank, lanyard, phone, cup,
|
||||||
|
clipboard, umbrella, camera, sign, flashlight, wand, radio, …); and **20 props**
|
||||||
|
(octagonal bollard and bin, slatted bench, hydrant, news box, planters, street lamp,
|
||||||
|
street and palm trees, A-frame sign, wall lantern, cone, crowd barrier, stanchion,
|
||||||
|
handrail, podium, double door, marquee, sedan).
|
||||||
|
|
||||||
|
`NpcAvatar` selects a character by role/seed, tints the five contract slots, and
|
||||||
|
attaches gear at the documented points; `WorldBuilder` loads the library props
|
||||||
|
with colliders/avoidance and drives their emissive parts from the day/night
|
||||||
|
cycle. The single `npc_base.glb` and primitive props remain offline fallbacks.
|
||||||
|
Contract and integration notes: [`docs/blender-assets.md`](docs/blender-assets.md).
|
||||||
|
|
||||||
|
| Characters | Props |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
```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.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
- **Surface textures:** the six tiles above, loaded by `src/game/materials.gd` and `WorldBuilder`.
|
||||||
|
- **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
|
||||||
|
```
|
||||||
|
|
||||||
### Screen design references
|
### Screen design references
|
||||||
|
|
||||||
Screen mockups and screenshot comparison use `tools/screen_designs.py`. It turns the
|
Screen mockups and screenshot comparison use `tools/screen_designs.py`. It turns the
|
||||||
@@ -187,23 +311,60 @@ screen descriptions in `docs/` (README captions, ADRs 0007–0010, `PLAN.md`, an
|
|||||||
`architecture.md`) into reference images under `docs/screens/designs/`, then uses
|
`architecture.md`) into reference images under `docs/screens/designs/`, then uses
|
||||||
those images as the expected state to check the game's captures.
|
those images as the expected state to check the game's captures.
|
||||||
|
|
||||||
```bash
|
Each screen is tagged `scene` (a realistic first-person 3D world view) or `ui`
|
||||||
# Deterministic wireframes (offline, no API key)
|
(an in-game panel overlaid on a softened scene), and the generator appends a
|
||||||
python3 tools/screen_designs.py generate --draft
|
shared art-direction suffix so the frames read as one coherent game. Requests use
|
||||||
|
a 3:2 landscape source and crop to the 16:9 capture dimensions.
|
||||||
|
|
||||||
# Photorealistic mockups via OpenRouter (requires OPENROUTER_API_KEY)
|
```bash
|
||||||
|
# Export the built-in scenarios to editor-authorable .tres resources
|
||||||
|
godot --headless --path . --script res://tools/export_venues.gd
|
||||||
|
|
||||||
|
# List the catalogue (name, kind, whether a reference design exists)
|
||||||
|
python3 tools/screen_designs.py list
|
||||||
|
|
||||||
|
# Deterministic wireframes (offline, no API key); existing designs are kept
|
||||||
|
# unless --force is passed, so an offline run never replaces an AI mockup
|
||||||
|
python3 tools/screen_designs.py generate --draft
|
||||||
|
python3 tools/screen_designs.py generate --draft --only wand --force
|
||||||
|
|
||||||
|
# Realistic 3D scenes and in-game screen designs via OpenRouter
|
||||||
|
# (requires OPENROUTER_API_KEY)
|
||||||
python3 tools/screen_designs.py generate
|
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
|
# Compare docs/screenshots/*.png against the designs
|
||||||
python3 tools/screen_designs.py compare
|
python3 tools/screen_designs.py compare
|
||||||
python3 tools/screen_designs.py compare --json
|
python3 tools/screen_designs.py compare --json
|
||||||
|
|
||||||
|
# Ask a vision model whether each capture realises its design's screen and layout
|
||||||
|
# (requires OPENROUTER_API_KEY; the repo .env is read automatically)
|
||||||
|
python3 tools/screen_designs.py judge
|
||||||
|
python3 tools/screen_designs.py judge --model openai/gpt-4o-mini --json
|
||||||
|
python3 tools/screen_designs.py judge --only 03- # one capture
|
||||||
|
python3 tools/screen_designs.py judge --dry-run # build requests, no network
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The catalogue covers 21 screens: the eleven README captures plus the start menu,
|
||||||
|
pause overlay, settings, shift review, radial menu, emergency radio, wand
|
||||||
|
screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival,
|
||||||
|
body-camera timeline, and the escalation HUD. All 19 have an AI reference 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
|
`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
|
frame, and large layout/palette/detail distance. It is a regression gate, not a
|
||||||
semantic judge — pixel and perceptual metrics cannot decide whether a frame's
|
semantic judge — pixel and perceptual metrics cannot decide whether a frame's
|
||||||
composition is *right*, so a vision-model pass is the documented follow-up (see
|
composition is *right*.
|
||||||
`TODO.md`). Regenerate the designs after changing screen layout, then re-run
|
|
||||||
|
`judge` covers that gap: it sends each capture together with its concept mockup to
|
||||||
|
a vision model and asks whether the capture realises the same screen and layout
|
||||||
|
intent. Art style, fidelity, fonts, and placeholder text are explicitly ignored.
|
||||||
|
Verdicts are classed as `match`, `review` (a mismatch below `--fail-confidence`,
|
||||||
|
default 0.6 — expected when a concept mockup is more stylised than the game), or
|
||||||
|
`diff` (a confident mismatch, which exits non-zero). Regenerate the designs after changing screen layout, then re-run
|
||||||
`compare` after regenerating the game screenshots with
|
`compare` after regenerating the game screenshots with
|
||||||
`src/test/capture_screenshots.gd`.
|
`src/test/capture_screenshots.gd`.
|
||||||
|
|
||||||
@@ -230,7 +391,7 @@ godot --headless --path . --script res://src/test/test_runner.gd
|
|||||||
# Save a screenshot to user:// (windowed)
|
# Save a screenshot to user:// (windowed)
|
||||||
godot --path . --script res://src/test/take_screenshot.gd
|
godot --path . --script res://src/test/take_screenshot.gd
|
||||||
|
|
||||||
# Windowed control smoke: focus ring + action wheel, saves PNGs, exits 1 on failure
|
# Windowed control smoke: focus circle, action wheel, wheel cycling + right-click clear; saves PNGs, exits 1 on failure
|
||||||
godot --path . --script res://src/test/smoke_focus_radial.gd
|
godot --path . --script res://src/test/smoke_focus_radial.gd
|
||||||
|
|
||||||
# Session-shell smoke: menu → shift → pause → settings → menu (headless or windowed)
|
# Session-shell smoke: menu → shift → pause → settings → menu (headless or windowed)
|
||||||
@@ -239,17 +400,40 @@ godot --headless --path . --script res://src/test/smoke_session_shell.gd
|
|||||||
# PLAN-verb smoke: recognize, request reason, body camera, warning, blocking
|
# PLAN-verb smoke: recognize, request reason, body camera, warning, blocking
|
||||||
godot --headless --path . --script res://src/test/smoke_plan_verbs.gd
|
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
|
# Crowd smoke: rubbernecking on aggression, panic clearing the entrance
|
||||||
godot --headless --path . --script res://src/test/smoke_crowd.gd
|
godot --headless --path . --script res://src/test/smoke_crowd.gd
|
||||||
|
|
||||||
# Emergency smoke: unjustified call, investigation note, evacuation aftermath
|
# Emergency smoke: unjustified call, investigation note, evacuation aftermath
|
||||||
godot --headless --path . --script res://src/test/smoke_emergency.gd
|
godot --headless --path . --script res://src/test/smoke_emergency.gd
|
||||||
|
|
||||||
|
# Body-camera timeline smoke: review → timeline → step through → back to review
|
||||||
|
godot --headless --path . --script res://src/test/smoke_timeline.gd
|
||||||
|
|
||||||
|
# Audio smoke: crowd bed follows mood, radio open/close crackle
|
||||||
|
godot --headless --path . --script res://src/test/smoke_audio.gd
|
||||||
|
|
||||||
|
# Scenario-content smoke: wand refusal, celebrity crowd spike
|
||||||
|
godot --headless --path . --script res://src/test/smoke_scenario_content.gd
|
||||||
```
|
```
|
||||||
|
|
||||||
The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor.
|
The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor.
|
||||||
|
|
||||||
### Controls
|
### 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 |
|
| Input | Action |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| W/A/S/D + mouse | Move and look |
|
| W/A/S/D + mouse | Move and look |
|
||||||
@@ -269,7 +453,9 @@ The unit test runner is the project's own lightweight harness (no editor plugin
|
|||||||
| C | Handheld wand screening (wand venues only) |
|
| C | Handheld wand screening (wand venues only) |
|
||||||
| A / D | Allow / deny entry during inspection or wand check |
|
| A / D | Allow / deny entry during inspection or wand check |
|
||||||
| V | Cycle venue (debug shortcut; restart via V restarts the current shift) |
|
| V | Cycle venue (debug shortcut; restart via V restarts the current shift) |
|
||||||
| Tab | Cycle target among nearby NPCs (aim-free alternative) |
|
| Tab / mouse wheel ↓ / ↑ | Cycle the target among nearby NPCs, nearest first (aim-free; the target stays selected) |
|
||||||
|
| Right click (nothing open) | Clear the focused person and return to aim-based focus |
|
||||||
|
| Shift (hold) | Precision walk at roughly half speed for close work at the door |
|
||||||
| G | Toggle flashlight (widens NPC perception at night) |
|
| G | Toggle flashlight (widens NPC perception at night) |
|
||||||
| B | Toggle the body camera; a formal warning switches it on automatically |
|
| B | Toggle the body camera; a formal warning switches it on automatically |
|
||||||
| L | Lock / unlock the restricted door (thwarts forced entry) |
|
| L | Lock / unlock the restricted door (thwarts forced entry) |
|
||||||
@@ -288,6 +474,8 @@ The full arc42-style architecture documentation lives in [`docs/architecture.md`
|
|||||||
- [`MEMORY.md`](MEMORY.md) — Persistent session notes.
|
- [`MEMORY.md`](MEMORY.md) — Persistent session notes.
|
||||||
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
|
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
|
||||||
- [`docs/adr/`](docs/adr/) — Architecture Decision Records.
|
- [`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
|
## Development Status
|
||||||
|
|
||||||
@@ -296,12 +484,12 @@ Implemented so far:
|
|||||||
- Godot 4.3 project scaffold, input map, procedural world builder
|
- 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`
|
- 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
|
- Three authored scenarios (Hollywood Boulevard building, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools
|
||||||
- Headless unit tests: **113 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)
|
- Headless unit tests: **208 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
|
- 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
|
- 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 gold ring 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`)
|
- Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`)
|
||||||
- Session shell: venue start menu (keyboard + mouse), Esc pause overlay (resume / restart / settings / quit to menu), settings panel with mouse-look sensitivity and HUD font scale persisted to `user://settings.json`, and "Run another shift" returning to the venue menu
|
- Session shell: venue start menu (keyboard + mouse), Esc pause overlay (resume / restart / settings / quit to menu), settings panel with mouse-look sensitivity and HUD font scale persisted to `user://settings.json`, and "Run another shift" returning to the venue menu
|
||||||
- World presentation: skyline backdrop rendered with an alpha material, walkable step and landing colliders, larger marquee text, and larger focus ring / name tag
|
- World presentation: skyline backdrop rendered with an alpha material, walkable step and landing colliders, larger marquee text, and larger focus circle / name tag
|
||||||
- Routine traffic despawns after a while at its waypoint so the crowd cap no longer fills with idle standers
|
- Routine traffic despawns after a while at its waypoint so the crowd cap no longer fills with idle standers
|
||||||
- PLAN verbs: eye contact (distinct Level 1 cue), recognizing a person as a player decision recorded as access evidence, a body-camera flag that a formal warning arms (and `B` toggles), blocking that physically stations the guard between the person and the door, and a Level 3 request-reason ask answered from the NPC's `dialogue_lines`
|
- PLAN verbs: eye contact (distinct Level 1 cue), recognizing a person as a player decision recorded as access evidence, a body-camera flag that a formal warning arms (and `B` toggles), blocking that physically stations the guard between the person and the door, and a Level 3 request-reason ask answered from the NPC's `dialogue_lines`
|
||||||
- Incidents are now actually opened on focus and closed at shift end, so player actions, access evidence, escalation levels, and unresolved interactions reach the review (previously every recorded event was dropped)
|
- Incidents are now actually opened on focus and closed at shift end, so player actions, access evidence, escalation levels, and unresolved interactions reach the review (previously every recorded event was dropped)
|
||||||
@@ -309,10 +497,27 @@ Implemented so far:
|
|||||||
- Distant skyline ring: four alpha-scissored planes at 200 m read as a hazy city on the horizon (the baked backdrop used to show as a black slab), with a larger ground plane so the buildings meet the street
|
- Distant skyline ring: four alpha-scissored planes at 200 m read as a hazy city on the horizon (the baked backdrop used to show as a black slab), with a larger ground plane so the buildings meet the street
|
||||||
- Flashlight aims straight ahead and lights the person/door being checked instead of the ground in front of the guard
|
- Flashlight aims straight ahead and lights the person/door being checked instead of the ground in front of the guard
|
||||||
- Emergency aftermath: the `evacuation` outcome locks the door and clears the entrance, an emergency call that the situation did not justify opens an investigation and applies the false-report hit, and the shift review names the responder outcome and any investigations
|
- Emergency aftermath: the `evacuation` outcome locks the door and clears the entrance, an emergency call that the situation did not justify opens an investigation and applies the false-report hit, and the shift review names the responder outcome and any investigations
|
||||||
|
- Body-camera timeline: the shift review opens a text timeline of every recorded incident event (time, person, action/outcome) that you step through with the arrow keys or buttons. No live 3D replay yet
|
||||||
|
- NPC theater: seated guests sit on the steps with bent legs, influencers and tourists raise a camera prop while photographing, performers sway their sign, and filming visitors pan slowly — all once they reach their intent's waypoint
|
||||||
|
- Audio: a looping procedural crowd bed (filtered murmur) whose volume follows `CrowdSystem.mood`, plus radio open/close crackle. All generated at runtime, no audio assets, and safe with a dummy audio server
|
||||||
|
- Accessibility: the escalation cue shows a shape meter plus the numeric level (`Escalation: Blocking [###--] L3/5`) so it reads in grayscale, and the HUD font scale from settings is applied at boot by resizing fonts (not Control.scale, which broke the right-aligned layout)
|
||||||
|
- Editor-ready venues: `tools/export_venues.gd` writes `content/venues/<id>.tres` from `ScenarioFactory`, and the `.tres` files load back with events, profiles, policy, phases, door lines, and locations intact (the factory stays the source of truth)
|
||||||
|
- Localization safety: `Strings.fmt` validates the placeholder count before formatting and fails safe (warns, returns the template) instead of raising an engine error, `Strings.placeholder_count` ignores `%%`, and `Strings.validate()` lints the table for trailing `%`, unsupported conversions, and mixed positional/sequential placeholders
|
||||||
|
- Authored scenario content: Joke Shop reservation-without-ID, borrowed/mismatched ID, a guest who refuses the wand screen, and a late arrival during a performance; Studio filming-boundary repeats and a celebrity arrival that draws a small crowd; Building events that point the player at the body camera and request-reason verbs
|
||||||
- Visual polish pass (v0.2.0): phase-based lighting (sun/sky/ambient per shift phase), street lamps with night-mode bulbs and omni lights, glowing marquee signage, crosswalk, planters and low-poly street trees, NPC height/brightness variety, denser routine traffic — day and late-night screenshot sets
|
- 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)
|
- Assets pass: authored textures in `assets/textures/` (brick facade, concrete, asphalt, awning, skyline, vignette, HUD icons, access-card art) generated by `tools/generate_textures.py`, soft sun shadows, humanoid NPC rigs with archetype props + procedural walk bob and attention head-turn, inspection artifact cards, escalation vignette, mode icons, and a body-camera overlay (REC + shift clock)
|
||||||
|
- Rendering pass (v0.3.0 work, [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md)): one material module with roughness/metallic profiles per surface, ACES tonemapping, glow, aerial fog and colour grading driven by the shift phase, correctly scaled brick, framed facade windows that light up at night, door trim and handles, a curb and lane dashes, and the glowing marquee/lamp bulbs. NPCs gained per-role clothing palettes, caps/hoods/backpacks/sunglasses/lanyards, hands and shoes, and an arm/leg walk swing
|
||||||
|
- NPC characters (v0.5.0): articulated limb rigs, layered outfits, hand items, face/hair variety, idle behaviour (phone, clipboard, crossed arms, look-around), escalation gestures, readable reaction symbols, door-queue offsets, and three LOD tiers with a billboard impostor
|
||||||
|
- Player movement (v0.5.0): separate acceleration/braking, step-up assist, head bob and landing dip (toggleable), precision-walk FOV/chip/tick, surface-aware footsteps, gamepad support, and persistent key/button remapping in settings
|
||||||
|
- Prop collision and placement (v0.4.0): every prop fits a static collider from its mesh bounds and publishes a navigation obstacle, so the guard and guests cannot walk through benches, bins, or bollards; placements are grouped into zones with a validator, and deterministic variants add rotation, scale, and small clutter
|
||||||
|
- Prop atlas (v0.3.1): `tools/model_pipeline.py` packs each prop's palette into one power-of-two colour atlas plus a metallic/roughness atlas and emits a single material with per-vertex UVs, so a prop is one draw material rather than one per part; `audit` enforces the contract
|
||||||
|
- Generated prop models (v0.3.0 work): `tools/model_pipeline.py` builds bollards, benches, bins, planters, newspaper boxes and a hydrant as `.glb` props with per-part materials, loaded by `WorldBuilder` with a procedural fallback; the optional AI concept step (`model_pipeline.py concept`) supplies per-prop colour palettes
|
||||||
|
- Environment pass (v0.6.0): 1024² brick/concrete/asphalt with derived tangent normal maps, 256² baked AO maps for every Blender character/gear/prop, and library props whose bulbs/LEDs/sign faces follow the day/night phase
|
||||||
|
- NPC characters + library (v0.6.0): `NpcAvatar` loads one of 8 Blender-authored skinned variants (role/seed selected) with five tintable material slots, `Rig/Head` focus anchor, and attach-point gear; `npc_base.glb` and the procedural rig remain fallbacks. `tools/blender_assets.py` builds/audits the character, gear, and 20-prop library; `WorldBuilder` loads the replacements and new street dressing (lamp, trees, palms, planters, barriers, podium, doors, marquee, sedan, handrail) with colliders, navigation obstacles, and phase-driven emissives
|
||||||
|
- Web build: `build/gig-line-web-<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: body-camera replay timeline, 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
|
### Smoke Test Checklist
|
||||||
|
|
||||||
@@ -322,9 +527,9 @@ Not yet implemented: body-camera replay timeline, editor-based GUT integration,
|
|||||||
4. The guard's camera sits at eye height and the guard stands on the landing, not inside the building.
|
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).
|
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.
|
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.
|
8. NPCs spawn and approach the entrance during the first minutes.
|
||||||
9. Looking at an NPC inside the focus cone shows a gold head ring, the person's name, and the HUD target line.
|
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.
|
10. `Space` or left click opens the action wheel; `A`/`D` highlight a slice, `Space` confirms it, `Esc`/right click cancels without a log entry.
|
||||||
11. `E`/`C` open the inspection/wand panels from the wheel and freeze movement until they close; walking never fires the wand.
|
11. `E`/`C` open the inspection/wand panels from the wheel and freeze movement until they close; walking never fires the wand.
|
||||||
12. Pressing `Q` produces a reaction log entry and reputation feedback.
|
12. Pressing `Q` produces a reaction log entry and reputation feedback.
|
||||||
@@ -345,7 +550,10 @@ Not yet implemented: body-camera replay timeline, editor-based GUT integration,
|
|||||||
27. NPCs route around the building using the navigation mesh (no stuck-at-wall behavior during a smoke run).
|
27. NPCs route around the building using the navigation mesh (no stuck-at-wall behavior during a smoke run).
|
||||||
28. An unjustified emergency call (calm person, no weapon) logs an investigation and costs reputation.
|
28. An unjustified emergency call (calm person, no weapon) logs an investigation and costs reputation.
|
||||||
29. After an evacuation outcome the door is locked and NPCs are leaving the entrance.
|
29. After an evacuation outcome the door is locked and NPCs are leaving the entrance.
|
||||||
30. When the shift timer reaches 0, the shift review screen shows grade, incident summary, responder outcomes, investigations, and persisted shift history; "Run another shift" returns to the venue menu.
|
30. The shift review's "Body camera timeline" button steps through the recorded events (Left/Right or the buttons) and Esc returns to the review.
|
||||||
|
31. The escalation label shows a `[###--] L3/5` shape meter as well as the level name, and stays readable in grayscale.
|
||||||
|
32. Changing "HUD font scale" in Settings resizes the HUD text and persists across a restart.
|
||||||
|
33. When the shift timer reaches 0, the shift review screen shows grade, incident summary, responder outcomes, investigations, and persisted shift history; "Run another shift" returns to the venue menu.
|
||||||
|
|
||||||
## Development Workflow
|
## Development Workflow
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +1,233 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
Godot 4.3 prototype. Release **v0.2.5** is tagged. Headless suite **113/113**, plus
|
Godot 4.3 prototype. Latest release **v0.5.0** (NPC characters and player movement). Headless
|
||||||
`smoke_session_shell.gd`, `smoke_plan_verbs.gd`, `smoke_crowd.gd`, `smoke_emergency.gd`, and the
|
suite **222/222** plus **68** Python tooling tests; the session, PLAN-verb, crowd, emergency,
|
||||||
windowed focus/radial smoke.
|
timeline, audio, scenario-content, props, NPC-budget, movement, and windowed focus/radial smokes
|
||||||
|
pass. Anonymous downloads are enabled on the host.
|
||||||
|
|
||||||
Host `REQUIRE_SIGNIN_VIEW` stays deferred.
|
The scheduled presentation pass is complete (see `DONE.md`). The next scheduled pass is the
|
||||||
|
graphics-quality work in [`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md).
|
||||||
|
|
||||||
## Body-camera replay
|
> **Blender asset library integrated (2026-11-01):** 8 NPC character variants, 18 gear pieces,
|
||||||
|
> and 20 street/venue props are now loaded by `NpcAvatar` and `WorldBuilder` from
|
||||||
|
> `assets/models/blender/` (manifest as source of truth, see
|
||||||
|
> [`docs/blender-assets.md`](docs/blender-assets.md)). The procedural rig and primitive props
|
||||||
|
> remain offline fallbacks; AO/ORM baking is the remaining Phase 4 follow-up.
|
||||||
|
|
||||||
- [ ] Shift review: a timeline of recorded incident events (ADR-0005 already stores them).
|
## Graphics quality — Blender-authored NPC characters (v0.6.0)
|
||||||
Scrub or step through; no live 3D replay required in the first version.
|
|
||||||
|
|
||||||
## NPC theater
|
Detailed plan: [`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md) · [ADR-0013](docs/adr/0013-blender-authored-npc-characters.md)
|
||||||
|
|
||||||
Intents `sit` / `photo` / `film` / `perform` already pick waypoints. ADR-0010 rig (face/hair)
|
### Phase 0 — Toolchain spike and contract freeze
|
||||||
is in the working tree. Bodies still idle-stand at those spots.
|
|
||||||
|
|
||||||
- [ ] Sit pose at `steps_end`. Photo: brief raise of the influencer/tourist prop. Performer
|
- [x] Verify `flatpak run org.blender.Blender --version` reports Blender 5.2.x and runs headless in this shell.
|
||||||
stays put and plays the hat/sign.
|
- [x] Run a minimal headless Blender export using an absolute `--python` path (sandbox cwd is `/app/blender`); confirm the output file lands where expected.
|
||||||
|
- [x] Record the exact working invocation (flatpak + absolute paths) in `MEMORY.md`.
|
||||||
|
- [x] Freeze the NPC asset contract in `docs/plan-graphics-v0.6.md` and ADR-0013: file paths, bone names, material slots, triangle budget.
|
||||||
|
- [x] Fix the material-slot set (`skin` / `cloth` / `pants` / `accessory` / `hair`) and the ORM packing (R=occlusion, G=roughness, B=metallic).
|
||||||
|
- [x] Choose and record the initial tier-0 triangle/vertex budget (≤ ~6000 tris / ~4000 verts) in the plan.
|
||||||
|
- [x] Spike: Godot 4.3 imports the `.glb` as a `PackedScene` with a `Skeleton3D`; no clips are needed because `_animate()` drives the bones at runtime.
|
||||||
|
|
||||||
## Audio
|
### Phase 1 — Blender authoring + export pipeline
|
||||||
|
|
||||||
`AudioFeedback` is sine beeps. DONE called that "crowd ambience"; it is not.
|
- [x] Author the low-poly base humanoid in Blender: torso, articulated arms/legs, head with a face front, hands, shoes.
|
||||||
|
- [x] Rig with an armature whose bones mirror the node contract (`Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`); origin at the feet, +Y up.
|
||||||
|
- [x] UV-unwrap the mesh into a power-of-two layout, one region per material slot.
|
||||||
|
- [x] Save the editable source as `assets/models/npc/npc_base.blend` (committed).
|
||||||
|
- [x] Add `tools/npc_pipeline.py` with `list` / `build` / `audit` subcommands, run through the Blender flatpak.
|
||||||
|
- [x] `build` exports `assets/models/generated/npc_base.glb` plus the material textures.
|
||||||
|
- [x] Bake neutral albedo per slot, normal maps (fabric weave, denim, knit), and the packed ORM map.
|
||||||
|
- [x] Bake ambient occlusion into the albedo or the ORM R channel (the Compatibility renderer has no SSAO).
|
||||||
|
- [x] `audit` enforces UVs in [0,1], expected bone names, triangle/vertex budget, feet at y≈0, and material-slot presence.
|
||||||
|
- [x] Reuse the `model_pipeline.py` colour-space discipline (linear factors vs sRGB textures) in the exporter.
|
||||||
|
- [x] Run `godot --headless --path . --import` and confirm `npc_base.glb` imports without errors.
|
||||||
|
|
||||||
- [ ] Loop a low crowd bed that rises with `CrowdSystem.mood`.
|
### Phase 2 — AI-assisted texturing
|
||||||
- [ ] Radio open/close crackle on the existing radio panel, still headless-safe (no-op without
|
|
||||||
an audio server).
|
|
||||||
|
|
||||||
## Accessibility
|
- [x] Generate seamless clothing albedos (shirt, trousers, jacket, hi-vis vest, coat, dress) with `tools/asset_pipeline.py generate` (OpenRouter, `openai/gpt-5-image-mini`) — written to `assets/textures/npc/ai/` (512×512).
|
||||||
|
- [x] Tile-edge blending on each fabric — applied automatically by `generate --role tile` (`make_seamless`); the `tile` audit's edge check confirms continuity.
|
||||||
|
- [x] Run `asset_pipeline.py audit` on the outputs — 6 good, 0 bad (role `tile`).
|
||||||
|
- [x] Commit the validated textures under `assets/textures/npc/ai/` (tracked in the v0.6.0 asset commit).
|
||||||
|
- [x] Derive per-role outfit palettes via `model_pipeline.dominant_colors` — written to `assets/textures/npc/palette.json` (`npc_pipeline.py palette`).
|
||||||
|
- [x] Bake normal/roughness procedurally and layer the AI albedos on top — `npc_pipeline.py prepare` + Blender `build` keeps the AI albedo for `cloth`/`pants` and regenerates normal/ORM; GLB audit ok (1204 tris / 2488 verts).
|
||||||
|
- [x] Regenerate the staged captures and run `screen_designs.py judge` after the Phase 3 integration (11 good / 0 bad in `compare`; judge: no diff).
|
||||||
|
|
||||||
- [ ] Escalation colors that still read in grayscale (shape or label, not hue alone).
|
### Phase 3 — Godot integration
|
||||||
- [ ] HUD font scale in settings.
|
|
||||||
|
|
||||||
## Scenario content still missing vs PLAN lists
|
- [x] Add `NpcAvatar._load_model()` that instantiates the imported `npc_base.glb` `PackedScene`.
|
||||||
|
- [x] Keep `_build_body()` as the offline fallback; choose per-avatar and record `loaded_model` / `fallback_avatar` for tests.
|
||||||
|
- [x] Map `outfit_for()` onto the model's material slots so seed variety, `layer_for`, `hand_item_for`, and archetype colors survive.
|
||||||
|
- [x] Keep accessory attachments (cap / hood / backpack / sunglasses / lanyard) under `Rig` / `Head`.
|
||||||
|
- [x] Add `SurfaceMaterials` baked-map recipes (`npc()` / `skin()` / `cloth()`); stop calling `Materials.flat()` for the body.
|
||||||
|
- [x] Add `_set_limb_rotation()` so `_animate()` / `_idle_layer()` write to bones or pivots; keep `Rig/Head` findable by the focus cone.
|
||||||
|
- [x] Update `test_npc_character`, `test_npc_theater`, and `test_target_cycle` for the skinned path (or document why they stay on the fallback).
|
||||||
|
- [x] Re-measure `smoke_npc_budget.gd`; tune `LOD_TIERS` if the skinned rig is heavier up close.
|
||||||
|
- [x] Re-verify the focus-cone smoke (`smoke_focus_radial.gd`) and the movement smoke.
|
||||||
|
|
||||||
- [ ] Joke Shop: guest on the reservation list without ID; borrowed/mismatched ID; guest
|
### Phase 4 — Environment and lighting lift ✅
|
||||||
refuses wand screening; late arrival during a performance.
|
|
||||||
- [ ] Studio: filming-boundary repeat (influencer ignores a posted line); celebrity-arrival
|
|
||||||
crowd spike (more rubberneckers, not only one `arrive` spawn).
|
|
||||||
- [ ] Building: "activate body camera" and "request reason" once those verbs exist, used in
|
|
||||||
at least one authored event.
|
|
||||||
|
|
||||||
## Distribution follow-up (host config, deferred)
|
- [x] Bake AO for the prop set through the Blender library (replacements above) or into the ORM/albedo maps behind the existing `WorldBuilder` contract; keep the procedural fallback.
|
||||||
|
- [x] Regenerate higher-resolution brick / concrete / asphalt textures via `asset_pipeline.py` with normal maps, keeping the current UV scales; apply the same map discipline to the library's street materials.
|
||||||
|
- [x] Evaluate a desktop-only Forward+ export preset (SSAO/SSIL/volumetrics); keep the Web preset on GL Compatibility with `thread_support=false`.
|
||||||
|
- [x] Record the renderer decision in an ADR addendum or `MEMORY.md`.
|
||||||
|
- [x] Regenerate `docs/screenshots/` with `capture_screenshots.gd` and run `screen_designs.py compare`.
|
||||||
|
|
||||||
Verified via API+host credentials (2026-10-31):
|
### Phase 5 — Verification and release
|
||||||
|
|
||||||
- [x] Repo is **public** (`private:false`) and account has admin rights.
|
- [x] Headless suite green: fallback build, outfit determinism, `Rig/Head` presence, material recipe assertions, `npc_pipeline.py audit`, and `blender_assets.py audit` + manifest tests (skip Blender builds with a reason when Blender is absent).
|
||||||
- [x] Release `v0.2.1` now carries all five artifacts (portable binaries + NSIS installer +
|
- [x] Windowed smokes pass: `smoke_npc_budget.gd`, `smoke_movement.gd`, `smoke_focus_radial.gd`, `smoke_props.gd`.
|
||||||
AppImage).
|
- [x] `screen_designs.py compare` and `judge` show no regressions; `blender_assets.py sheet` contact sheets reviewed for the character/gear/prop library.
|
||||||
- [ ] **Remaining host setting**: instance-wide `[service] REQUIRE_SIGNIN_VIEW=true` makes
|
- [ ] Bump `config/version` to `0.6.0`.
|
||||||
anonymous browsing/downloads redirect to login. Host admin: `REQUIRE_SIGNIN_VIEW = false`
|
- [ ] Update `README.md`, `docs/architecture.md` (Level 2 gains `tools/npc_pipeline.py`), and mark ADR-0013 Accepted.
|
||||||
under `[service]` in `app.ini`, restart Gitea. Alternative: mirror downloads.
|
- [ ] Publish the release per `docs/RELEASING.md`.
|
||||||
|
|
||||||
## Open Polish
|
## Visual alignment with rendered mockups
|
||||||
|
|
||||||
- [ ] Keep `%` placeholders stable in `Strings.fmt` when adding keys (positional formatting).
|
The AI-rendered references in `docs/screens/designs/` (start menu, pause, settings,
|
||||||
|
shift review, radial menu, HUD, scene views) describe the intended look of every
|
||||||
|
screen. This pass reorganizes how generated images are stored, makes the game load
|
||||||
|
the proposed interface/scene art from disk, and aligns the selection screens, HUD,
|
||||||
|
and scene visuals with those mockups — verified with
|
||||||
|
`tools/screen_designs.py compare` + `judge`.
|
||||||
|
|
||||||
## Screen design references (QA tooling)
|
### Storage restructure
|
||||||
|
|
||||||
- [x] `tools/screen_designs.py generate` writes one reference design per screen
|
- [ ] Decide the canonical runtime image root (e.g. `assets/images/` for UI/scene art) and record it in `MEMORY.md` and an ADR.
|
||||||
(from the `docs/` descriptions) to `docs/screens/designs/`, with an offline
|
- [ ] 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).
|
||||||
`--draft` wireframe mode and an OpenRouter `--ai` mockup mode.
|
- [ ] Document one convention for game vs reference vs development images across `assets/textures/`, `assets/images/`, `assets/models/generated/`, `docs/screenshots/`, and `docs/screens/designs/`.
|
||||||
- [x] `tools/screen_designs.py compare` checks `docs/screenshots/*.png` against the
|
- [ ] Re-run `godot --headless --path . --import` to regenerate `.import` sidecars after any move.
|
||||||
designs (aspect ratio, size, blank-frame, and layout/palette/detail drift).
|
- [ ] Confirm `.gitignore` still excludes development-only outputs (concepts, Blender sources, raw AI fabrics) under the new layout.
|
||||||
- [ ] Vision-model judge for semantic screenshot comparison. Pixel and perceptual
|
|
||||||
metrics cannot decide whether a frame's composition is correct; a follow-up
|
|
||||||
should send the capture + reference design to a vision model and ask whether
|
|
||||||
they depict the same screen.
|
|
||||||
|
|
||||||
## Tooling (when an editor workflow exists)
|
### Documentation path updates
|
||||||
|
|
||||||
- [ ] IDE/editor-based GUT integration. Until then the custom headless runner stays.
|
- [ ] Update `README.md` paths (screen-designs section, confirmed-assets links, the `assets/images/backgrounds` resize example).
|
||||||
- [ ] Optional later: author venues as `.tres` resources instead of `ScenarioFactory` functions.
|
- [ ] Update `docs/architecture.md`, `PLAN.md`, and ADR-0010/0011/0012/0013 references to `docs/screens/designs/`.
|
||||||
|
- [ ] Update `tools/screen_designs.py` docstrings and mark the old paths in `DONE.md` as historical.
|
||||||
|
|
||||||
|
### Code path updates
|
||||||
|
|
||||||
|
- [ ] Point `tools/screen_designs.py generate` at the new design output path and keep `compare`/`judge` reading the right locations.
|
||||||
|
- [ ] Update GDScript `preload()` paths (`materials.gd`, `hud.gd`, `world_builder.gd`) if the game textures move.
|
||||||
|
- [ ] Update `capture_screenshots.gd` / `capture_prop_gallery.gd` output directories if captures relocate.
|
||||||
|
|
||||||
|
### Load proposed interfaces + scene art from disk
|
||||||
|
|
||||||
|
- [ ] 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.
|
||||||
|
- [ ] Wire the start menu and venue selection screen to use the `start_menu` scene art.
|
||||||
|
- [ ] Wire the pause overlay, settings, shift review, radial menu, and HUD chrome to their reference images.
|
||||||
|
|
||||||
|
### Align in-game visuals
|
||||||
|
|
||||||
|
- [ ] Make the venue selection screen match the `start_menu` mockup (layout, palette, background).
|
||||||
|
- [ ] Make the HUD match the `targeting_npc` / `escalation_hud` mockups (focus ring, escalation meter, log, hints).
|
||||||
|
- [ ] Align the remaining panels (pause, settings, shift review, radio, wand, timeline) to their mockups.
|
||||||
|
- [ ] Align scene lighting, fog, and post-processing for `venue_overview` and `late_night_avenue`.
|
||||||
|
|
||||||
|
### Verify against the mockups
|
||||||
|
|
||||||
|
- [ ] Regenerate the staged captures and run `screen_designs.py compare` (no gross drift).
|
||||||
|
- [ ] Run `screen_designs.py judge` and clear every `diff` verdict (target `match`/`review`).
|
||||||
|
- [ ] Add the capture + compare step to CI or a repeatable smoke so drift is caught early.
|
||||||
|
|
||||||
|
## Improve in-game pop-ups
|
||||||
|
|
||||||
|
- [ ] Improve the visual appearance of in-game pop-ups (layout, button style, palette, icons, and text styling).
|
||||||
|
|
||||||
|
## 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/`, scene references to `docs/screens/concepts/`,
|
||||||
|
and trial captures and reports to `docs/screens/realism/<run>/`.
|
||||||
|
|
||||||
|
### Step 1 — Tooling (`tools/scene_concepts.py`)
|
||||||
|
|
||||||
|
- [ ] 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.
|
||||||
|
- [ ] `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.
|
||||||
|
- [ ] `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.
|
||||||
|
- [ ] `audit`: run `asset_pipeline.audit_image` on the processed candidates (`backdrop` /
|
||||||
|
`tile` roles) and fail on any bad result.
|
||||||
|
- [ ] `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.
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] 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.
|
||||||
|
- [ ] 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)
|
||||||
|
|
||||||
|
- [ ] 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.
|
||||||
|
- [ ] 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.
|
||||||
|
|
||||||
|
### Step 4 — Textures
|
||||||
|
|
||||||
|
- [ ] 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.
|
||||||
|
- [ ] Process them into 1024² game-ready tiles plus normal maps in
|
||||||
|
`assets/textures/ai/game_ready/v2/` and audit them.
|
||||||
|
|
||||||
|
### Step 5 — In-game trial captures (no game-code change)
|
||||||
|
|
||||||
|
- [ ] 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 `--skyline=` / `--brick=` / `--concrete=` / `--asphalt=` user arguments. The
|
||||||
|
swap finds materials by their current texture, so baseline and candidate frames use the
|
||||||
|
same camera. Writes to `user://screenshots/realism/<tag>/`.
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
- [ ] Run `scene_concepts.py realism` on baseline and candidate. Record the metric deltas and the
|
||||||
|
vision scores, and note which candidate textures/backdrops win.
|
||||||
|
- [ ] Note the remaining realism gaps (sky/fog, lighting, building geometry, NPC fidelity) as
|
||||||
|
follow-up tasks below.
|
||||||
|
|
||||||
|
### Step 7 — Adopt the winners (after the in-flight normal-map/AO work is committed)
|
||||||
|
|
||||||
|
- [ ] Point `WorldBuilder.T_SKYLINE` at the chosen v2 skyline (separate day/night textures through
|
||||||
|
`set_night_mode`), and retune `albedo_color` for the realistic strip.
|
||||||
|
- [ ] Switch `materials.gd` / `world_builder.gd` surface preloads to the chosen v2 tiles + normal
|
||||||
|
maps, keeping the current UV scales. Update `test_materials.gd` / `test_world.gd`.
|
||||||
|
- [ ] Regenerate `docs/screenshots/` with `capture_screenshots.gd`, then run
|
||||||
|
`screen_designs.py compare` + `judge` and `scene_concepts.py realism`. There must be no
|
||||||
|
`diff` verdict regressions, and realism scores must not drop.
|
||||||
|
- [ ] Update `README.md` (tool usage + gallery), `MEMORY.md` and `docs/architecture.md`.
|
||||||
|
|
||||||
|
## Future ideas (optional, not scheduled)
|
||||||
|
|
||||||
|
- [ ] Live 3D body-camera replay (the timeline is text-only today).
|
||||||
|
- [ ] Additional venues or seasonal events authored from the new `.tres` files.
|
||||||
|
- [ ] Real voice/dialogue audio (the crowd bed and radio crackle are procedural).
|
||||||
|
- [ ] Swap the primitive prop builder for an image-to-mesh model API behind the same
|
||||||
|
`assets/models/generated/<prop>.glb` contract.
|
||||||
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 30 KiB |