Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b0845bcce | ||
|
|
4cca2af2dd | ||
|
|
99f390078d | ||
|
|
a9fe6b9944 | ||
|
|
d4201caa62 | ||
|
|
6100d19a25 | ||
|
|
a7ca698f73 | ||
|
|
26d64baa01 | ||
|
|
151630e957 | ||
|
|
1b319c48b7 | ||
|
|
a478bfbf35 | ||
|
|
702f54ae53 | ||
|
|
bbf4693475 | ||
|
|
d7b0203502 | ||
|
|
da876e3545 | ||
|
|
1ee32215b0 | ||
|
|
2624b54b23 | ||
|
|
85d67ff8b1 | ||
|
|
47c57ddc76 | ||
|
|
3c07525351 | ||
|
|
4a672025a2 | ||
|
|
6acbc2b3a4 | ||
|
|
d7e523f63c | ||
|
|
26ecb93fd4 | ||
|
|
8997f59819 | ||
|
|
79270795a8 | ||
|
|
dc4bc6c511 | ||
|
|
c8b98fdb30 | ||
|
|
d96f53c285 | ||
|
|
b39fee6176 | ||
|
|
c2c93c28a5 | ||
|
|
3030c60ae1 | ||
|
|
cb65a5a601 | ||
|
|
06a4f5479f | ||
|
|
8032999a4d | ||
|
|
8582e496ff | ||
|
|
ae326ce970 | ||
|
|
00faf45e83 | ||
|
|
41d3511fc7 | ||
|
|
1efa5bc7d9 | ||
|
|
4745adeb13 | ||
|
|
15deb58b73 | ||
|
|
6a0bf70fdf | ||
|
|
f458551594 | ||
|
|
8f228b0c56 | ||
|
|
d307746351 | ||
|
|
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: |
|
||||||
|
|||||||
@@ -43,4 +43,33 @@ user://
|
|||||||
.gitea/logs/
|
.gitea/logs/
|
||||||
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
|
||||||
|
# Editable Blender sources are developer-only; keep the .gdignore marker tracked.
|
||||||
|
assets/models/blender_src/*.blend
|
||||||
|
# Realism pass (tools/scene_concepts.py): the candidate pool and per-run captures
|
||||||
|
# stay local; the validated picks are tracked under game_ready/v2/picks/ (ADR-0014),
|
||||||
|
# and each run commits its report.{json,md} + sheet.jpg. Concept references are
|
||||||
|
# committed in docs/screens/concepts/.
|
||||||
|
assets/textures/ai/game_ready/v2/*
|
||||||
|
!assets/textures/ai/game_ready/v2/picks/
|
||||||
|
docs/screens/realism/**/*.png
|
||||||
@@ -14,6 +14,14 @@ The project should be developed in small, reviewable steps. Agents must keep pla
|
|||||||
- `MEMORY.md` — Persistent session memory for important decisions, discoveries, unresolved questions, constraints, and useful implementation context.
|
- `MEMORY.md` — Persistent session memory for important decisions, discoveries, unresolved questions, constraints, and useful implementation context.
|
||||||
- `README.md` — Root-level documentation for the project. It should explain what the game is, how to work on it, how to run it, and where to find project information.
|
- `README.md` — Root-level documentation for the project. It should explain what the game is, how to work on it, how to run it, and where to find project information.
|
||||||
- `AGENTS.md` — This collaboration and agent workflow guide.
|
- `AGENTS.md` — This collaboration and agent workflow guide.
|
||||||
|
- `CHANGELOG.md` — The version history of user-facing changes. Keep an entry for each patch, minor, and major version.
|
||||||
|
- `docs/development-status.md` — The implemented-features list and what is not yet implemented. Update it whenever a feature lands, is renamed, or is removed.
|
||||||
|
- `docs/architecture.md` — The arc42-style architecture documentation (goals, constraints, building blocks, runtime views, decisions, risks).
|
||||||
|
- `docs/gameplay.md` — Gameplay systems, venues, and the escalation model.
|
||||||
|
- `docs/asset-tooling.md` — Asset pipelines, model tools, and screenshot gates.
|
||||||
|
- `docs/getting-started.md` — Setup, run/test commands, and the controls table.
|
||||||
|
- `docs/smoke-checklist.md` — The manual smoke-test checklist.
|
||||||
|
- `docs/adr/` — Architecture Decision Records.
|
||||||
|
|
||||||
## Required Workflow
|
## Required Workflow
|
||||||
|
|
||||||
@@ -39,6 +47,10 @@ All planned implementation work must be represented in `TODO.md`.
|
|||||||
- Use checkbox items for each concrete task.
|
- Use checkbox items for each concrete task.
|
||||||
- Keep tasks small enough to complete and review independently.
|
- Keep tasks small enough to complete and review independently.
|
||||||
- Add new tasks when discoveries expand the scope.
|
- Add new tasks when discoveries expand the scope.
|
||||||
|
- When starting a task, claim it for the agent workflow by changing `[ ]` to `[-]`. Keep it `[-]`
|
||||||
|
while implementation or verification is in progress; only change it to `[x]` after the work and
|
||||||
|
relevant checks are finished. If work is abandoned or blocked, leave a clear note and return the
|
||||||
|
item to `[ ]` (or add a follow-up task).
|
||||||
- Mark a task complete only after the implementation and relevant verification are finished.
|
- Mark a task complete only after the implementation and relevant verification are finished.
|
||||||
- If any verification check fails, do not mark the task complete. Fix the failure, or if it cannot be fixed, add a follow-up task to `TODO.md`, record the failure in `MEMORY.md`, and report it in the handoff summary.
|
- If any verification check fails, do not mark the task complete. Fix the failure, or if it cannot be fixed, add a follow-up task to `TODO.md`, record the failure in `MEMORY.md`, and report it in the handoff summary.
|
||||||
- Do not use prose status updates as a substitute for checkboxes.
|
- Do not use prose status updates as a substitute for checkboxes.
|
||||||
@@ -52,7 +64,7 @@ Recommended format:
|
|||||||
## Core Gameplay
|
## Core Gameplay
|
||||||
|
|
||||||
- [ ] Add player movement controls.
|
- [ ] Add player movement controls.
|
||||||
- [ ] Implement NPC approach behavior.
|
- [-] Implement NPC approach behavior. (claimed; implementation/verification in progress)
|
||||||
- [x] Define escalation levels.
|
- [x] Define escalation levels.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
@@ -71,6 +83,8 @@ When every task in a `TODO.md` section is complete:
|
|||||||
|
|
||||||
Do not move partially completed sections. Leave them in `TODO.md` until all of their tasks are complete, or split the section into smaller sections first.
|
Do not move partially completed sections. Leave them in `TODO.md` until all of their tasks are complete, or split the section into smaller sections first.
|
||||||
|
|
||||||
|
When a completed section represents shipped functionality, also record the new capability in `docs/development-status.md` so the implemented-features list stays accurate, and write a matching entry in `CHANGELOG.md` (see "Maintain `CHANGELOG.md`" below).
|
||||||
|
|
||||||
### 4. Maintain `MEMORY.md`
|
### 4. Maintain `MEMORY.md`
|
||||||
|
|
||||||
Use `MEMORY.md` for information that should persist between sessions but does not belong in the general plan or task list.
|
Use `MEMORY.md` for information that should persist between sessions but does not belong in the general plan or task list.
|
||||||
@@ -78,7 +92,7 @@ Use `MEMORY.md` for information that should persist between sessions but does no
|
|||||||
Record items such as:
|
Record items such as:
|
||||||
|
|
||||||
- Important design and architecture decisions
|
- Important design and architecture decisions
|
||||||
- Current implementation state
|
- Notable implementation state and gotchas (the canonical implemented-features list lives in `docs/development-status.md`, not here)
|
||||||
- Known bugs and workarounds
|
- Known bugs and workarounds
|
||||||
- Commands needed to run, test, or build the project
|
- Commands needed to run, test, or build the project
|
||||||
- Decisions that future agents should not revisit unnecessarily
|
- Decisions that future agents should not revisit unnecessarily
|
||||||
@@ -128,9 +142,27 @@ The root-level `README.md` is the project’s public-facing technical and projec
|
|||||||
- Links to `PLAN.md`, `TODO.md`, `DONE.md`, `MEMORY.md`, and this file where appropriate
|
- Links to `PLAN.md`, `TODO.md`, `DONE.md`, `MEMORY.md`, and this file where appropriate
|
||||||
- Known limitations and contribution guidance
|
- Known limitations and contribution guidance
|
||||||
|
|
||||||
Update `README.md` whenever setup instructions, commands, project structure, or user-facing behavior changes.
|
Update `README.md` whenever setup instructions, commands, project structure, or user-facing behavior changes. The README is intentionally concise and links to the `docs/` files; keep `docs/development-status.md` (and the other docs it links to) synchronized with the implementation rather than pasting detail back into the README.
|
||||||
|
|
||||||
### 7. Implement in Small Increments
|
### 7. Maintain `CHANGELOG.md`
|
||||||
|
|
||||||
|
The root-level `CHANGELOG.md` records the user-facing changes for every release. It exists so players and contributors can see what changed between versions without reading the commit log.
|
||||||
|
|
||||||
|
- Add an entry for every patch, minor, and major version change.
|
||||||
|
- Follow Semantic Versioning (`major.minor.patch`): bump major for breaking or vision-level changes, minor for new features, and patch for fixes and small polish.
|
||||||
|
- Group each entry under `Added`, `Changed`, `Fixed`, and `Removed` headings, and date every entry.
|
||||||
|
- Describe changes in user-facing terms, not as raw commit messages.
|
||||||
|
- Add the entry when its work ships. If the version number is not yet known, record it under `Unreleased` and move it into a numbered version when the version is tagged.
|
||||||
|
|
||||||
|
When a full level-2 section in `TODO.md` is implemented, completed, and moved to `DONE.md`:
|
||||||
|
|
||||||
|
1. Verify the work and mark every checkbox in the section `[x]`.
|
||||||
|
2. Use the section heading and its completed items to describe the changes and write a `CHANGELOG.md` entry under the next appropriate version.
|
||||||
|
3. Move the section to `DONE.md`.
|
||||||
|
|
||||||
|
Do not move a completed section to `DONE.md` without adding its `CHANGELOG.md` entry.
|
||||||
|
|
||||||
|
### 8. Implement in Small Increments
|
||||||
|
|
||||||
For each task:
|
For each task:
|
||||||
|
|
||||||
@@ -138,9 +170,13 @@ For each task:
|
|||||||
2. Inspect the relevant files and dependencies.
|
2. Inspect the relevant files and dependencies.
|
||||||
3. Make the smallest coherent change that completes the task.
|
3. Make the smallest coherent change that completes the task.
|
||||||
4. Run the relevant tests, checks, or manual verification.
|
4. Run the relevant tests, checks, or manual verification.
|
||||||
5. Update documentation and `MEMORY.md` if the change creates lasting context.
|
5. Update documentation if the change creates lasting context: add features to `docs/development-status.md`, adjust the README links or other `docs/` files as needed, and record decisions/gotchas in `MEMORY.md`.
|
||||||
6. Mark the task complete.
|
6. Mark the task complete.
|
||||||
7. Move its section to `DONE.md` when the entire section is complete.
|
7. Move its section to `DONE.md` when the entire section is complete (after recording its `CHANGELOG.md` entry).
|
||||||
|
8. Commit the changes to the version control system with a descriptive message.
|
||||||
|
9. If a section from `TODO.md` has been fully completed, ensure its `CHANGELOG.md` entry is in place, then create a new version or tag in the version control system to mark the milestone.
|
||||||
|
10. Push the committed changes to the shared repository if applicable.
|
||||||
|
11. Note the new version or tag created in the version control system in any relevant documentation or handoff summary.
|
||||||
|
|
||||||
Avoid unrelated refactors unless they are required for the task or explicitly approved.
|
Avoid unrelated refactors unless they are required for the task or explicitly approved.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable user-facing changes to Gig Line. The format follows
|
||||||
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
|
||||||
|
Semantic Versioning.
|
||||||
|
|
||||||
|
## [0.7.0] — 2026-11-01
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Agent workflow now requires maintaining this changelog for every patch, minor, and major version.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Distinct venue geometry: the Joke Shop is now a narrow brick alley with a
|
||||||
|
shuttered facade, warm wall lantern, dumpster and AC clutter; the TV Studio
|
||||||
|
has a stone facade, bulb-lit canopy, glossy black car and a stanchion/barrier
|
||||||
|
line at the door.
|
||||||
|
- The README gallery includes captured Joke Shop and TV Studio views.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Venue geometry is data-driven through `WorldBuilder.VENUE_SPECS`; switching
|
||||||
|
venues rebuilds the world while keeping the guard and UI intact.
|
||||||
|
- Each venue has its own navigation bounds and concept-gallery mapping.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The Joke Shop no longer renders the Hollywood skyline or marquee.
|
||||||
|
- The TV Studio no longer uses the boulevard marquee or palm-lined layout.
|
||||||
|
|
||||||
|
## [0.6.1] — 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Per-phase AI skyline panoramas (day, dusk, night) replacing the flat box skyline.
|
||||||
|
- Higher-detail surface textures with normal maps (brick, concrete, asphalt, awning, and more).
|
||||||
|
- A runtime image root (`assets/images/`) with a UI and scene art loader; the start menu,
|
||||||
|
pause, settings, shift review, radio, wand, inspection, and radial menu are styled against
|
||||||
|
their mockups.
|
||||||
|
- NPC face and hand detail: eyes, nose, lips, ears, fingered hands, and cuffed sleeves.
|
||||||
|
- A staged NPC portrait capture for judging avatar quality up close.
|
||||||
|
- Gear-fit checks (`tools/blender_assets.py fit`) and regression tests for head and back gear.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Scene lighting, fog, sun angle, and night glow retuned to match the AI concept frames;
|
||||||
|
the Late phase uses wet asphalt.
|
||||||
|
- The flashlight is a tighter forward spot that reads in the dark.
|
||||||
|
- Sunglasses narrowed to sit inside the face silhouette; shoulder bag moved clear of the
|
||||||
|
coat and dress skirts; firefighter helmet lifted off the brow.
|
||||||
|
- The README now links to focused docs pages for gameplay, architecture, setup, asset
|
||||||
|
tooling, development status, and the manual smoke checklist; `PLAN.md` was compacted
|
||||||
|
into a design plan.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Sidewalk overexposure at the entrance reduced to match the concept target.
|
||||||
|
- The body-camera overlay no longer overlaps the shift/reputation/crowd status.
|
||||||
|
- Selecting a venue releases the menu input lock and recaptures the mouse.
|
||||||
|
- The shoulder bag no longer clips through the coat's skirt; the firefighter helmet no
|
||||||
|
longer cuts into the brow ridge; the portrait subject now faces the camera.
|
||||||
|
- `--portrait-type` is now parsed and applied instead of being ignored.
|
||||||
|
|
||||||
|
## [0.6.0] — 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Blender-authored character, gear and prop library with baked ambient occlusion.
|
||||||
|
- Higher-resolution environment textures with tangent normal maps.
|
||||||
|
- Phase-driven emissive details on library props, characters and street dressing.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The game was renamed from "Step Line" to "Gig Line".
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- NPCs now face their movement direction instead of walking backwards.
|
||||||
|
|
||||||
|
## [0.5.0] — 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Articulated NPC characters with layered outfits, hand items, face/hair variety,
|
||||||
|
idle behaviour, reaction symbols, queue offsets and three LOD tiers.
|
||||||
|
- Player acceleration/braking, step-up assist, head bob, landing dip,
|
||||||
|
precision-walk feedback and surface-aware footsteps.
|
||||||
|
- Remappable keyboard and gamepad controls.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Crowd roles now select deterministic character variants and props.
|
||||||
|
|
||||||
|
## [0.4.0] — 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Collision and navigation footprints for every street prop.
|
||||||
|
- Zoned prop placements with deterministic variants and small clutter.
|
||||||
|
|
||||||
|
## [0.3.3] — 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Staged in-game captures for the wand screening, escalation HUD, and body-camera timeline screens.
|
||||||
|
|
||||||
|
## [0.3.2] — 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- AI reference mockups for the seven remaining catalogue screens.
|
||||||
|
|
||||||
|
## [0.3.1] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Prop models drawn with a single texture atlas (one material per prop).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The Web build is listed in the README downloads table.
|
||||||
|
|
||||||
|
## [0.3.0] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Surface-material profiles, ACES tonemapping, glow, aerial fog and phase-based
|
||||||
|
colour grading.
|
||||||
|
- Framed facade windows, door trim, curb and lane markings.
|
||||||
|
- Generated street-furniture models and the Web export.
|
||||||
|
- Aim-free targeting, a focus cone and precision walk.
|
||||||
|
|
||||||
|
## [0.2.16] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Venues authorable as editor `.tres` resources.
|
||||||
|
|
||||||
|
## [0.2.15] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- A vision-model judge for comparing screenshots against reference designs.
|
||||||
|
|
||||||
|
## [0.2.14] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Anonymous release downloads enabled on the host.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `Strings.fmt` keeps positional placeholders stable when new keys are added.
|
||||||
|
|
||||||
|
## [0.2.13] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Missing scenario content: Joke Shop reservation/ID cases, wand refusal and late
|
||||||
|
arrival events, the studio filming-boundary repeat, and a celebrity-arrival crowd spike.
|
||||||
|
|
||||||
|
## [0.2.12] — 2026-10-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Release uploads fail loudly instead of leaving an empty release with a green job.
|
||||||
|
- Pruned releases restored and CI publishing verified end to end.
|
||||||
|
|
||||||
|
## [0.2.11] — 2026-10-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Releases publish using the runner's checkout credentials when no token or git
|
||||||
|
credentials exist.
|
||||||
|
|
||||||
|
## [0.2.10] — 2026-10-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Release publishing fixed with a CI token fallback.
|
||||||
|
|
||||||
|
## [0.2.9] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Accessibility pass: escalation feedback that reads in grayscale and a working HUD font scale.
|
||||||
|
|
||||||
|
## [0.2.8] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Procedural crowd ambience that rises with crowd mood, and a radio crackle.
|
||||||
|
|
||||||
|
## [0.2.7] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- NPC theater poses: sitting, taking photos, filming, and performing.
|
||||||
|
|
||||||
|
## [0.2.6] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- A body-camera timeline on the shift review for stepping through recorded events.
|
||||||
|
|
||||||
|
## [0.2.5] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Emergency aftermath: evacuation clears the entrance, and unjustified calls write
|
||||||
|
an investigation note with a false-report reputation hit.
|
||||||
|
|
||||||
|
## [0.2.4] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Crowd presentation: onlookers rubberneck at incidents and clear out during panic.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- NPC navigation issues resolved.
|
||||||
|
|
||||||
|
## [0.2.3] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Remaining PLAN actions: eye contact, "I recognize this person", a formal-warning
|
||||||
|
body-camera flag, blocking that stations the guard in front of the door, and
|
||||||
|
requesting a reason for entry.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- A dead incident-recording path.
|
||||||
|
|
||||||
|
## [0.2.2] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Session shell: startup menu with venue selection, pause overlay, settings
|
||||||
|
persistence, and a "run another shift" flow back to the menu.
|
||||||
|
- Control pass: focus cone, head ring, radial action menu, and named input actions.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Release publishing now uploads the installer and AppImage.
|
||||||
|
|
||||||
|
## [0.2.1] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Windows NSIS installer and Linux AppImage packaging; all exports embed the PCK.
|
||||||
|
- An AI texture generation mode in the asset pipeline.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- NSIS build fixed for Linux (absolute defines, space-free installed exe, BMP icon).
|
||||||
|
- Runtime hygiene: RefCounted cycle cleanup, full-length shifts, and a wand cooldown.
|
||||||
|
|
||||||
|
## [0.2.0] — 2026-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- First playable build of the security-guard simulation at the Hollywood Boulevard entrance.
|
||||||
|
- Core simulation: shift coordination, venue scenarios, event scheduling, NPC movement
|
||||||
|
and states, and escalation from observation through deterrence, warnings, and blocking.
|
||||||
|
- Access verification for wristbands, IDs, tickets, credentials, and guest lists, with
|
||||||
|
admit/redirect/hold/refuse decisions and feedback scoring.
|
||||||
|
- Escalation levels 0–5 with radio assistance and a hold-to-confirm emergency call.
|
||||||
|
- Incident recording, body-camera evidence, reputation scoring, crowd reactions, and
|
||||||
|
an end-of-shift review.
|
||||||
|
- Scenario content for Hollywood Boulevard, The Joke Shop, and the TV Studio, including
|
||||||
|
a mental-health crisis event.
|
||||||
|
- A protester NPC, the "call someone else" reaction, a lockable door, and a flashlight.
|
||||||
|
- Save/persistence, navmesh pathfinding, and JSON localization.
|
||||||
|
- Visual polish: phase lighting, street lamps, marquee, street dressing, and NPC variety.
|
||||||
|
- Detailed graphics: authored textures, a humanoid NPC rig, and HUD/inspection feedback.
|
||||||
|
- Downloadable Linux, Windows, and macOS builds published by CI.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- The dialogue tone-of-voice picker (reactions now depend on escalation level and NPC profile).
|
||||||
@@ -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,802 @@ 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.
|
||||||
|
|
||||||
|
## v0.6.0 Release
|
||||||
|
|
||||||
|
Completed 2026-11-01. `config/version` = `0.6.0`; ADR-0013 marked Accepted; CI published six
|
||||||
|
artifacts (Linux, Windows, macOS, NSIS installer, AppImage, Web) for tag `v0.6.0`.
|
||||||
|
|
||||||
|
- [x] Bumped the version and synchronised README/architecture/blender-assets/plan docs.
|
||||||
|
- [x] Headless **222/222**, Python **69/69**, all smokes, `compare` **11 good / 0 bad**.
|
||||||
|
- [x] Release verified: `https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.6.0`.
|
||||||
|
|
||||||
|
## Runtime Image Path Migration
|
||||||
|
|
||||||
|
Completed 2026-11-01. Historical note: the screen-design references originally lived only in
|
||||||
|
`docs/screens/designs/`; they were promoted to the runtime root `assets/images/ui/` under
|
||||||
|
ADR-0014. `docs/screens/designs/` remains a reference-only snapshot; `tools/screen_designs.py`
|
||||||
|
now reads and writes `assets/images/ui/`.
|
||||||
|
|
||||||
|
## Realistic Backgrounds, Textures and Scene References
|
||||||
|
|
||||||
|
Completed 2026-11-01 (part of the v0.6.x realism pass). The flat box skyline was replaced by
|
||||||
|
per-phase AI panoramas, the surface tiles by higher-detail v2 picks with normal maps, and the
|
||||||
|
lighting/exposure was retuned against AI concept frames. Evidence:
|
||||||
|
`docs/screens/realism/2026-11-01-adopt/` (`report.md`, `sheet.jpg`), `screen_designs.py compare`
|
||||||
|
**11 good / 0 bad**; headless **227/227**, Python **86/86**, all smokes.
|
||||||
|
|
||||||
|
### Realistic backgrounds, textures and scene references
|
||||||
|
|
||||||
|
Goal: replace the outdated flat skyline backdrop (`assets/textures/skyline.png`, black boxes
|
||||||
|
with dot windows) and the flat-looking surface tiles with AI-generated, more realistic
|
||||||
|
backgrounds and textures. Add a larger set of example 3D scene images as art direction, and
|
||||||
|
measure the in-game result against that target with the screenshot tools. Plan:
|
||||||
|
[`PLAN.md` → Visual Quality Direction → Realism pass](PLAN.md#realism-pass).
|
||||||
|
|
||||||
|
**Rule for every step: never overwrite an existing image.** New outputs get new paths. When a
|
||||||
|
path is taken, the tools pick the next free `-vN` suffix. Raw AI output stays on disk under
|
||||||
|
`assets/textures/ai/v2/raw/` (gitignored like the other raw AI folders). Processed game-ready
|
||||||
|
candidates go to `assets/textures/ai/game_ready/v2/`, a local pool where only the validated picks
|
||||||
|
are tracked (ADR-0014). Scene references go to `docs/screens/concepts/`, and trial captures and
|
||||||
|
reports to `docs/screens/realism/<run>/`.
|
||||||
|
|
||||||
|
#### Step 1 — Tooling (`tools/scene_concepts.py`)
|
||||||
|
|
||||||
|
- [x] Add a catalogue of prompts in three groups: `scene` (16:9 example 3D game frames),
|
||||||
|
`backdrop` (4:1 skyline panoramas with transparent sky), and `texture` (seamless PBR
|
||||||
|
surface tiles). Each entry has a name, prompt, role and target path.
|
||||||
|
- [x] `generate [--group] [--only] [--variants N]`: call the OpenRouter image API through
|
||||||
|
`generate_textures.generate_image` (key from the repo `.env`), save raw output, and never
|
||||||
|
overwrite (`next_free_path`). Print every file written.
|
||||||
|
- [x] `process`: turn raw backdrops into 2048×512 RGBA strips that tile horizontally (edge
|
||||||
|
crossfade, sky made fully transparent, hard alpha for the alpha-scissor material). Turn raw
|
||||||
|
textures into seamless 1024² tiles (`asset_pipeline.make_seamless`) plus a normal map
|
||||||
|
(`make_normal_maps.normal_map`). Skip outputs that already exist.
|
||||||
|
- [x] `audit`: run `asset_pipeline.audit_image` on the processed candidates (`backdrop` /
|
||||||
|
`tile` roles) and fail on any bad result.
|
||||||
|
- [x] `realism`: for each trial capture, report the Pillow detail metrics (edge density,
|
||||||
|
contrast, colour spread, saturation) next to the matching concept image. Optionally ask a
|
||||||
|
vision model (`google/gemini-2.5-flash-lite`) for 0–10 realism scores (lighting,
|
||||||
|
materials, texture detail, backdrop, overall) with concrete fixes. Write a new JSON +
|
||||||
|
Markdown report per run.
|
||||||
|
- [x] Unit tests (`tools/test_scene_concepts.py`): catalogue shape, the no-overwrite path
|
||||||
|
picker, backdrop/tile processing on synthetic images, metrics, and the judge payload/parse
|
||||||
|
(offline, no network).
|
||||||
|
|
||||||
|
#### Step 2 — Example 3D scene images
|
||||||
|
|
||||||
|
- [x] Generate 3D-game-style example frames for every venue and time of day: Hollywood entrance
|
||||||
|
(morning, golden hour, rainy night), boulevard skyline view (day, dusk), Joke Shop alley
|
||||||
|
(evening, close-up of the door), studio stage door (night arrival), sidewalk material
|
||||||
|
close-up, entrance steps and podium, crowd-control barrier line, and a responder arrival
|
||||||
|
wide shot.
|
||||||
|
- [x] Review the set and record the visual targets each frame sets (sky, haze, material detail,
|
||||||
|
backdrop depth) in the report.
|
||||||
|
|
||||||
|
#### Step 3 — Backgrounds (replace the outdated skyline)
|
||||||
|
|
||||||
|
- [x] Generate skyline panoramas for day, dusk and night (lit windows): Hollywood/Los Angeles
|
||||||
|
mid-rise blocks, palm silhouettes, the hills on the horizon, a transparent sky, and a
|
||||||
|
straight-on view, in several variants each.
|
||||||
|
- [x] Process them into `assets/textures/ai/game_ready/v2/skyline_{day,dusk,night}*.png`,
|
||||||
|
seamless horizontally for the 3× UV repeat around the backdrop ring. Strips are 2048×1024
|
||||||
|
(2:1, one UV repeat of the 400×60 m ring is about 133×60 m). 12 candidates, 11 audit
|
||||||
|
clean; `skyline_day-v3` misses the seam limit by 0.6. Variants 1–2 contain garbled
|
||||||
|
"Hollywood" lettering, so the prompt now forbids signs. Picks: `skyline_day-v4`,
|
||||||
|
`skyline_dusk-v3`, `skyline_night-v4`.
|
||||||
|
|
||||||
|
#### Step 4 — Textures
|
||||||
|
|
||||||
|
- [x] Generate higher-detail seamless tiles: red brick facade, granite/limestone facade
|
||||||
|
(studio), sidewalk concrete pavers with joints, asphalt with aggregate, wet night asphalt,
|
||||||
|
stone steps, striped awning canvas, and stucco wall.
|
||||||
|
- [x] Process them into 1024² game-ready tiles plus normal maps in
|
||||||
|
`assets/textures/ai/game_ready/v2/` and audit them (16 tiles + 16 normals, all good). The
|
||||||
|
`awning_canvas` seam crossfade smears the stripes at the tile edge; it needs a
|
||||||
|
stripe-aligned crop.
|
||||||
|
|
||||||
|
#### Step 5 — In-game trial captures (no game-code change)
|
||||||
|
|
||||||
|
- [x] Add `src/test/capture_realism.gd`. It boots the Hollywood venue, captures a fixed shot
|
||||||
|
list (entrance day, boulevard/skyline day, boulevard night), and can swap textures at
|
||||||
|
runtime from `--swap=OLD=NEW` / `--tint=` / `--normal=` user arguments (the first version used
|
||||||
|
`--skyline=`/`--brick=`/… flags; `--swap` is path-based and also covers night stages). The
|
||||||
|
swap finds materials by their original texture path, so baseline and candidate frames use
|
||||||
|
the same camera. Writes to the `--out=` directory.
|
||||||
|
- [x] Capture the baseline (current assets) and the candidate set (v2 skyline + textures), and
|
||||||
|
copy them into `docs/screens/realism/<run>/{baseline,candidate}/`.
|
||||||
|
|
||||||
|
#### Step 6 — Compare against the realistic target
|
||||||
|
|
||||||
|
- [x] Run `scene_concepts.py realism` on baseline and candidate. Record the metric deltas and the
|
||||||
|
vision scores, and note which candidate textures/backdrops win. Run
|
||||||
|
`docs/screens/realism/2026-11-01-v2/` (`report-v2.md`, `sheet.jpg`) gives these mean
|
||||||
|
judge scores: backdrop 2.6 → 3.6 (v2 day + night skyline) → **4.0** (v2 day + dusk
|
||||||
|
skyline for the Evening phase), texture detail 1.2 → 1.6–1.75, overall 1.6 → 2.0. Single
|
||||||
|
scores swing by ±1–2 between runs, so read them as trends. The `characters` score
|
||||||
|
moved from 3.0 to 1.0 with identical NPCs, which shows the noise.
|
||||||
|
- [x] Note the remaining realism gaps (sky/fog, lighting, building geometry, NPC fidelity) as
|
||||||
|
follow-up tasks below.
|
||||||
|
|
||||||
|
#### Step 6b — Follow-ups from the 2026-11-01 realism report
|
||||||
|
|
||||||
|
- [x] Fix sidewalk overexposure: lowered the day sun (0.78→0.62), exposure (0.8→0.6) and ambient
|
||||||
|
(0.42→0.22), and darkened the sidewalk/street albedo tints. The like-for-like ground
|
||||||
|
luminance in `docs/screens/realism/2026-11-01-adopt/` went from 173/162/132 to 95/92/73
|
||||||
|
(concept targets 78/100/100). The entrance shot stays ~20 % brighter than the concept: a
|
||||||
|
residual, tracked in the follow-ups below.
|
||||||
|
- [x] Make the Evening/Late phases darker and lit by practical lights (marquee, lamps,
|
||||||
|
windows), and add wet-asphalt roughness at night, as in the rain-night concept. Evening is
|
||||||
|
now dusk (`sun 0.3→0.12`, `exposure 0.95→0.72`, dark violet sky), Late is near-dark, and
|
||||||
|
`WorldBuilder.set_phase()` swaps the street to the wet asphalt map in Late.
|
||||||
|
- [x] Choose the skyline per phase (day → `skyline_day`, Evening → `skyline_dusk`, Late →
|
||||||
|
`skyline_night`) and stop the scene fog from bleaching it. The backdrop is now a
|
||||||
|
`fog_disabled` shader, because Godot 4.3 has no `fog_enabled` on `StandardMaterial3D`. The
|
||||||
|
first dusk panorama was a red floodlit skyline that clashed with the Evening sky, so a
|
||||||
|
calmer prompt produced `skyline_dusk-v5` (blue-grey towers, lit windows).
|
||||||
|
- [x] Raise the backdrop plane (60 m → 90 m) and use 2 UV repeats so the skyline is a band with
|
||||||
|
proportions near the 2:1 panorama, not a thin strip at the horizon.
|
||||||
|
- [x] Add facade depth (plinth, string courses, cornice, brick pilasters, window glazing bars)
|
||||||
|
and replace the blob trees and shrubs with denser foliage meshes: `_shrub()` now builds
|
||||||
|
many small flat-shaded clumps plus outer clumps that break the silhouette
|
||||||
|
(`street_tree` 1472→2592 tris), with fresh AO maps for `street_tree`, `planter_box` and
|
||||||
|
`round_planter`.
|
||||||
|
- [x] Re-cut `awning_canvas` so its stripes align at the tile edge: `stripe_period()` measures the
|
||||||
|
repeat and `align_stripes()` resamples to a whole number of periods (edge crossfade smeared
|
||||||
|
the stripes before). `audit_striped_tile()` checks the wrap step against the harshest
|
||||||
|
interior step instead of the flat edge-difference rule.
|
||||||
|
- [x] Capture shots from the Joke Shop and the studio too: `capture_realism.gd` now runs five
|
||||||
|
stages (day, evening, late, joke_shop, studio) with 9 shots and a `--stages=` subset. The
|
||||||
|
venues share the Hollywood geometry, so those two compare lighting, not layout.
|
||||||
|
|
||||||
|
#### Step 7 — Adopt the winners
|
||||||
|
|
||||||
|
- [x] Track the validated picks in place (ADR-0014). `.gitignore` now ignores the candidate pool
|
||||||
|
and un-ignores `game_ready/v2/picks/`, where `scene_concepts.py promote` copies the eight
|
||||||
|
validated candidates under stable names (`skyline_{day,dusk,night}`, `brick_facade`,
|
||||||
|
`sidewalk_pavers`, `asphalt`, `asphalt_wet`, `awning_canvas`) plus normal maps.
|
||||||
|
- [x] Point `WorldBuilder` at the chosen v2 skylines (separate day/dusk/night textures through
|
||||||
|
the new `set_phase()`), and retune the tints per phase.
|
||||||
|
- [x] Switch `materials.gd` / `world_builder.gd` surface preloads to the chosen v2 tiles + normal
|
||||||
|
maps, keeping the current UV scales (`Materials.asphalt()` gained a `wet` variant). Updated
|
||||||
|
`test_materials.gd` (pick paths, wet vs dry) and `test_world.gd` (shader backdrop, panorama
|
||||||
|
per phase, wet street).
|
||||||
|
- [x] Regenerate `docs/screenshots/` with `capture_screenshots.gd` (11/11), then run
|
||||||
|
`screen_designs.py compare` (**11 good / 0 bad**) and `judge` (5 match, 2 review, 4 diff —
|
||||||
|
the diffs are HUD/mockup layout gaps from the "Visual alignment" section, not realism
|
||||||
|
regressions; the default flash-lite judge model is currently failing with a provider 400, so
|
||||||
|
the run used `--model google/gemini-2.5-flash`). `scene_concepts.py realism` on
|
||||||
|
`docs/screens/realism/2026-11-01-adopt/` (3 judge repeats per shot) gives a like-for-like
|
||||||
|
improvement on every sub-score: overall 0.8→1.0, lighting 0.8→1.0, materials 0.8→1.0,
|
||||||
|
texture 0.8→1.0, backdrop 0.6→1.6, and the Pillow metric gap 0.444→0.348.
|
||||||
|
- [x] Update `README.md` (tool usage + gallery), `MEMORY.md` and `docs/architecture.md`.
|
||||||
|
|
||||||
|
## Realism Follow-ups — Lighting, Flashlight, NPC Detail
|
||||||
|
|
||||||
|
Completed 2026-11-01. The follow-ups left over after the texture/skyline adoption, split off from
|
||||||
|
`TODO.md` when each one was finished. Evidence: `docs/screens/realism/2026-11-01-npc/`
|
||||||
|
(`report.md`, `sheet.jpg`), the portrait capture, and the refreshed `docs/screenshots/` gallery
|
||||||
|
(`screen_designs.py compare` **11 good / 0 bad**). Headless **228/228**, Python **86/86**.
|
||||||
|
|
||||||
|
### Ground exposure at the entrance
|
||||||
|
|
||||||
|
- [x] Fixed the over-bright entrance ground: the morning sun now sits at 34° elevation (was 50°)
|
||||||
|
with a phase `sun_yaw`, so the facade shades the near sidewalk and the props cast long
|
||||||
|
shadows. Ground luminance went 95 → **83** against the concept's 78 (it was 173 before the
|
||||||
|
realism pass), with the boulevard at 82 and the street at 81. Day haze warmed and
|
||||||
|
thickened (`fog_color` 0.87/0.83/0.76, density 0.0034, energy 0.5) and the night glow went
|
||||||
|
to 1.15.
|
||||||
|
|
||||||
|
### Flashlight readability
|
||||||
|
|
||||||
|
- [x] Made the flashlight read in the dark: the cone went 30° → **14°** with a 20 m range,
|
||||||
|
forward aim, `light_energy` 4.2 and `spot_attenuation`/`spot_angle_attenuation` 0.9, so it
|
||||||
|
lights the door instead of washing the facade.
|
||||||
|
- [x] Staged the two night captures in LATE (`elapsed_s` 760) instead of dusk in
|
||||||
|
`capture_screenshots.gd`; the judge moved `06-night-flashlight` from **diff (1.00) to
|
||||||
|
review (0.10)**.
|
||||||
|
- [x] Rejected a fake additive beam twice (cylinder + UV ramp, then a vertical-fade
|
||||||
|
`ShaderMaterial`): on GL Compatibility a first-person camera inside the cone renders it as
|
||||||
|
a filled disc with a bright far ring. `Materials.light_beam()` and the mesh were removed;
|
||||||
|
the visible *shaft* is parked behind the Forward+ preset in `TODO.md`.
|
||||||
|
- [x] Guarded the aim with `test_flashlight_aims_a_tight_forward_spot` (lamp on the head, straight
|
||||||
|
ahead, cone ≤ 20°, starts off).
|
||||||
|
|
||||||
|
### NPC faces, hands and sleeves
|
||||||
|
|
||||||
|
- [x] Rebuilt the avatar detail so skin and clothing stop reading as low-poly at conversation
|
||||||
|
distance: each eye has a sclera, iris and upper lid; added a brow ridge, cheekbones, a nose
|
||||||
|
bridge and tip, ears with lobes, and split upper/lower lips. Hands have four fingers plus a
|
||||||
|
thumb, and every sleeve ends in a cuff ring.
|
||||||
|
- [x] Raised `CHAR_MAX_VERTS` 4000 → **5000** (documented in the code and
|
||||||
|
`docs/blender-assets.md`) after measuring 16 avatars at ~6.9 ms of a 50 ms frame on a single
|
||||||
|
skinned mesh, so `npc_coat` fits at 4807 vertices; the character range is now 3,974–5,026
|
||||||
|
triangles and 3,744–4,807 vertices. Body decimation 0.36 → 0.26.
|
||||||
|
- [x] Rebuilt all 8 variants and re-baked their AO maps (`assets/models/blender/npc/*_ao.png`),
|
||||||
|
re-imported, and kept `blender_assets.py audit` at exit 0.
|
||||||
|
- [x] Added a staged portrait capture: `capture_realism.gd` gained a `portrait` stage that
|
||||||
|
freezes one NPC, parks the crowd out of frame, stands the subject on open sidewalk, turns
|
||||||
|
it into the camera and puts the guard on the sunlit side; `--portrait-type=N` picks a role.
|
||||||
|
It is the only capture where a face fills the frame, and it feeds the new
|
||||||
|
`npc_portrait_day` concept (`tools/scene_concepts.py` `SHOTS`).
|
||||||
|
- [x] Regenerated the gallery shots and re-ran the judge: like-for-like overall 2.33 → 2.44,
|
||||||
|
lighting 3.0 → 3.22, materials 2.22 → 2.44. The `characters` sub-score stayed flat
|
||||||
|
(~2.2–2.3) because the target is a photoreal face, so avatar quality is judged from the
|
||||||
|
portrait rather than the aggregate.
|
||||||
|
- [x] Re-checked the `screen_designs.py judge` default model (`google/gemini-2.5-flash-lite`):
|
||||||
|
it works but intermittently answers HTTP 400 "API key not valid" (one shot in ten on
|
||||||
|
2026-11-01); `--model google/gemini-2.5-flash` remains the fallback.
|
||||||
|
|
||||||
|
## Visual Alignment — UI Paths and Art Loader
|
||||||
|
|
||||||
|
Completed 2026-11-01.
|
||||||
|
|
||||||
|
|
||||||
|
### Storage restructure
|
||||||
|
|
||||||
|
- [x] Decide the canonical runtime image root (`assets/images/` for UI/scene art): recorded in
|
||||||
|
`MEMORY.md` and [ADR-0014](docs/adr/0014-runtime-image-root.md).
|
||||||
|
- [x] Move the reference mockups from `docs/screens/designs/` into the runtime root (keep `docs/screens/designs/` as a docs-only copy or replace it with the new path).
|
||||||
|
- [x] Document one convention for game vs reference vs development images across `assets/textures/`, `assets/images/`, `assets/models/generated/`, `docs/screenshots/`, and `docs/screens/designs/` (in `docs/architecture.md`, ADR-0014).
|
||||||
|
- [x] Re-run `godot --headless --path . --import` to regenerate `.import` sidecars after any move.
|
||||||
|
- [x] Confirm `.gitignore` still excludes development-only outputs (concepts, Blender sources, raw AI fabrics) under the new layout; Blender `.blend` sources are now untracked and ignored.
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation path updates
|
||||||
|
|
||||||
|
- [x] Update `README.md` paths (screen-designs section, confirmed-assets links, the `assets/images/scenes` resize example).
|
||||||
|
- [x] Update `docs/architecture.md`, `PLAN.md`, and ADR references: docs/screens/designs/ is now explicitly the reference-only snapshot; runtime art lives in assets/images/ (ADR-0014).
|
||||||
|
- [x] Update `tools/screen_designs.py` docstrings and mark the old paths in `DONE.md` as historical.
|
||||||
|
|
||||||
|
|
||||||
|
### Code path updates
|
||||||
|
|
||||||
|
- [x] Point `tools/screen_designs.py generate` at the new design output path and keep `compare`/`judge` reading the right locations.
|
||||||
|
- [x] Update GDScript `preload()` paths if game textures move (no runtime material/model textures moved; UI art added under assets/images/).
|
||||||
|
- [x] Update capture output directories if captures relocate (none did; QA captures stay in docs/screenshots/).
|
||||||
|
|
||||||
|
|
||||||
|
### Load proposed interfaces + scene art from disk
|
||||||
|
|
||||||
|
- [x] Add a central UI/scene art loader (e.g. `src/game/ui/art.gd`) that loads the mockup-derived images from the new runtime root, with a procedural fallback when a file is missing.
|
||||||
|
- [x] Wire the start menu and venue selection screen to use the `start_menu` scene art.
|
||||||
|
- [x] Wire the pause overlay to `pause_overlay` scene art (backdrop + opaque panel cover).
|
||||||
|
- [x] Wire the settings panel to `settings_panel` art.
|
||||||
|
- [x] Wire the shift-review screen to `shift_review` art (backdrop + opaque panel cover).
|
||||||
|
- [x] Wire the wand popup to `wand_screening` art.
|
||||||
|
- [x] Style the radial menu and finish the remaining HUD chrome against their reference images.
|
||||||
|
|
||||||
|
|
||||||
|
### Verify against the mockups
|
||||||
|
|
||||||
|
- [x] Regenerate the staged captures and run `screen_designs.py compare` (11 good, 0 bad; verified in a clean HEAD worktree).
|
||||||
|
- [x] Run `screen_designs.py judge` and clear every `diff` verdict (7 match, 4 review, 0 diff).
|
||||||
|
- [x] Add the capture + compare step as a repeatable smoke so drift is caught early (`tools/check_screenshots.sh`).
|
||||||
|
|
||||||
|
|
||||||
|
## Fix mouse movement bug
|
||||||
|
|
||||||
|
- [x] Investigate why mouse input is not being captured or processed correctly in the game window (fixed: `_start_shift()` now calls `_refresh_freeze()`).
|
||||||
|
|
||||||
|
|
||||||
|
## Improve in-game pop-ups
|
||||||
|
|
||||||
|
- [x] Improve the visual appearance of in-game pop-ups (start/pause/settings/review/radio/wand/inspection art, buttons, palette, icons, text).
|
||||||
|
|
||||||
|
## Visual Alignment With Rendered Mockups
|
||||||
|
|
||||||
|
Completed 2026-11-01. Every screen with an AI reference now loads or matches its mockup: the
|
||||||
|
runtime image root (ADR-0014), the UI/scene art loader, the menus/panels/HUD, and — last — the
|
||||||
|
scene lighting, fog and post-processing, which had been held until the realism pass landed.
|
||||||
|
`screen_designs.py compare` **11 good / 0 bad**; judge on the two scene shots: `01-venue-overview`
|
||||||
|
**match** (0.90), `05-late-night-avenue` **review** (0.10), 0 diff. Lighting: morning sun 34°
|
||||||
|
elevation with a phase `sun_yaw`, warm day haze (`fog_color` 0.87/0.83/0.76, density 0.0034,
|
||||||
|
energy 0.5), stronger night glow, street albedo 0.62. Remaining differences (camera height,
|
||||||
|
marquee text, hand-authored tree shapes) are content, not grading.
|
||||||
|
|
||||||
|
### Visual alignment with rendered mockups
|
||||||
|
|
||||||
|
#### Align in-game visuals
|
||||||
|
|
||||||
|
- [x] Make the venue selection screen match the `start_menu` mockup (layout, palette, background).
|
||||||
|
- [x] Make the HUD match the `targeting_npc` / `escalation_hud` mockups (focus ring, escalation meter, log, hints).
|
||||||
|
- [x] Align the pause, settings, and shift-review panels to their mockups.
|
||||||
|
- [x] Align the radio and wand panels to their mockups.
|
||||||
|
- [x] Align the body-camera timeline to its mockup.
|
||||||
|
- [x] Align scene lighting, fog, and post-processing for `venue_overview` and `late_night_avenue`: lowered the morning sun to 34° (`sun_yaw` is now a phase value), warmed and thickened the day haze, raised the night glow, and brightened the street albedo. `judge` moved `01-venue-overview` to **match** (0.90) and `05-late-night-avenue` from diff to **review** (0.10); `compare` stays 11 good / 0 bad.
|
||||||
|
- [x] Fix the body-camera REC/clock overlay overlapping the shift/rep/crowd status in the top-right HUD.
|
||||||
|
|
||||||
|
## Documentation Consistency
|
||||||
|
|
||||||
|
Completed 2026-11-01. `PLAN.md` was compacted from the historical implementation log into a
|
||||||
|
product/design plan (674 → 187 lines): escalation, NPC/venue content and design constraints stay,
|
||||||
|
while the completed control/rendering pass specs were folded into a short Presentation and
|
||||||
|
Controls section. `README.md` now reflects the implemented venues, Blender/v2 asset pipeline, the
|
||||||
|
custom test runner, the Web target, the current release state, and the capture smoke. The
|
||||||
|
architecture's version, runtime-art loader, asset/tooling blocks, Gitea CI/build/publish pipeline,
|
||||||
|
Web deployment and release artifact list were brought up to date. `MEMORY.md` was cleaned of
|
||||||
|
obsolete uncommitted/active-pass claims and now points at the current follow-ups.
|
||||||
|
|
||||||
|
- [x] Re-check `PLAN.md`/`MEMORY.md` against the current build, update `README.md` and `docs/architecture.md`, and compact the historical PLAN sections.
|
||||||
|
|
||||||
|
## NPC gear fit (v0.6.1)
|
||||||
|
|
||||||
|
Completed 2026-11-01. Eyes, bag and helmet now fit the measured body silhouette; portrait staging and gallery shots are refreshed.
|
||||||
|
|
||||||
|
The 2026-11-01 detail pass fixed the bodies but not the props worn on them. In the portrait
|
||||||
|
capture (`--stages=day,portrait`, see the report in `docs/screens/realism/2026-11-01-npc/`) the
|
||||||
|
sunglasses span the whole upper face and the shoulder bag cuts through `npc_coat`'s skirt. Blender sources for gear
|
||||||
|
live in `tools/blender_assets.py` (`_build_gear()`), the contract in `docs/blender-assets.md`
|
||||||
|
(gear budget 1500 tris, `GEAR_ATTACH` points in `NpcAvatar`).
|
||||||
|
|
||||||
|
- [x] Measure the fit in Blender, not by eye: `python3 tools/blender_assets.py fit` prints each
|
||||||
|
character's measured head width and hip/skirt radius, then every head/back/chest gear
|
||||||
|
bounding box with head-band status or skirt clearance. Current measurements: head width
|
||||||
|
0.315 m; coat skirt 0.225 m, dress 0.260 m; sunglasses 0.282 m (0.90x head); shoulder-bag
|
||||||
|
x-clearance -0.025 m on the coat (radial -0.031 m on the dress).
|
||||||
|
- [x] Narrow `sunglasses` from 0.28 m to ~0.20 m. The hinges moved to ±0.075 (0.035 box), the
|
||||||
|
arms to ±0.098, and the lenses are 0.024 m radius at the same `y=-0.152`, `z=0.035` plane.
|
||||||
|
Rebuilt GLB/AO: measured width **0.202 m** (0.64x head), still inside the head band.
|
||||||
|
Also fixed `capture_realism.gd` so `--portrait-type=N` is actually parsed (it was documented
|
||||||
|
but ignored).
|
||||||
|
- [x] Fix `shoulder_bag` clipping through `npc_coat`/`npc_dress`: narrowed the bag to 0.06 m and
|
||||||
|
moved it outboard (centre x=0.292); the body's inner face is now 0.262 m, clearing the coat
|
||||||
|
(0.225) by 0.037 m and the dress (0.260) by 0.002 m. The strap's lower attach points sit on
|
||||||
|
the same outboard line. `fit` now measures the bag body separately from the torso strap.
|
||||||
|
- [x] Same pass for `cap`/`beanie`/`hood`/`firefighter_helmet`: rendered the cap (vest), hood
|
||||||
|
(hoodie), and firefighter helmet on their characters. Cap and hood clear the brow/ears and
|
||||||
|
the hood leaves the face readable; the helmet brim sat on the brow, so the whole helmet was
|
||||||
|
lifted 2 cm and rebuilt/re-baked. Beanie uses the same capped-shell sizing as the cap and is
|
||||||
|
currently unused; it is covered by the upcoming headgear regression check.
|
||||||
|
- [x] Rebuild the gear (`build --only <18 gear names>`) and re-bake AO (`tools/bake_ao.py --only
|
||||||
|
<same>`), then `godot --headless --path . --import` (clean). Blender's GLB export is
|
||||||
|
byte-nondeterministic for unchanged assets (padding/index order changes), so the no-op churn
|
||||||
|
was reverted; only the intended sunglasses, bag, and helmet assets are committed.
|
||||||
|
- [x] Add a regression test: `tools/test_blender_assets.py` limits the glasses to 0.70x head
|
||||||
|
width and requires positive bag clearance on both coat and dress;
|
||||||
|
`test_npc_character.test_head_and_back_gear_clear_the_body_silhouette` loads the gear on
|
||||||
|
six roles and checks head-parent/width/brow bounds plus back-parent/0.26 m outer radius.
|
||||||
|
- [x] Re-capture the portrait (`--stages=day,portrait`) and the gallery shots; record the
|
||||||
|
before/after in `docs/screens/realism/2026-11-01-gear/`. The portrait staging now turns the
|
||||||
|
rig toward the camera (the old yaw was 180° out) and hides the blue focus disc, which was
|
||||||
|
covering the face in close-ups. `check_screenshots.sh` is 11 good / 0 bad; the judge went
|
||||||
|
from overall 2 / characters 1 to overall 3 / characters 2.
|
||||||
|
- [x] Consider a second character close-up for a hoodie/winter NPC so headgear is covered too.
|
||||||
|
`--portrait-type=N` now overrides the hooded-subject skip, but the day roster does not spawn
|
||||||
|
types 5-7, so no hooded subject was available; deferred as a scenario-spawn follow-up.
|
||||||
|
|
||||||
|
## Venue geometry and the committed gallery (v0.6.1)
|
||||||
|
|
||||||
|
Completed 2026-11-01. All three venues now build distinct geometry from `WorldBuilder.VENUE_SPECS`; the gallery has Joke Shop and TV Studio views (13 good / 0 bad). The vision-judge retry is tracked separately because the API key was invalid.
|
||||||
|
|
||||||
|
Today all three venues share the Hollywood `WorldBuilder` geometry, so the Joke Shop and studio
|
||||||
|
concepts are only compared on lighting (noted in `tools/scene_concepts.py` `SHOTS`). They also
|
||||||
|
have no stage in the committed `docs/screenshots/` gallery, only in `capture_realism.gd`.
|
||||||
|
|
||||||
|
- [x] Extract a geometry spec per venue instead of branching inline: `WorldBuilder.VENUE_SPECS`
|
||||||
|
now holds facade/stair/street/nav dimensions, window/pilaster rows, guard spawn, feature
|
||||||
|
flags and prop placements for Hollywood, Joke Shop and TV Studio. Unknown ids fall back to
|
||||||
|
Hollywood; `test_venue_geometry_specs_are_distinct_and_defaulted` checks the narrow Joke Shop
|
||||||
|
facade, disabled skyline/marquee, studio bulb-canopy flag and fallback.
|
||||||
|
- [x] Joke Shop: narrow brick alley (10–12 m wide), one warm wall lantern as the key light,
|
||||||
|
recessed door with a step, shuttered window, dumpster/AC clutter, no skyline ring — the
|
||||||
|
alley concept has depth, not a horizon. `_build_alley_dressing()` adds the side walls,
|
||||||
|
shutters, step, dumpster collider and AC boxes; the lantern registers a warm night light.
|
||||||
|
- [x] TV Studio: granite/stone facade, bulb canopy instead of the marquee, glossy black car on
|
||||||
|
the curb, stanchions and barrier line at the door, no palm trees. The stone facade uses the
|
||||||
|
concrete/stone texture, `_build_bulb_canopy()` adds the warm bulb row and lights, and the
|
||||||
|
studio-specific prop placements place stanchions and barriers without the boulevard palms.
|
||||||
|
- [x] Keep navigation working: `WorldBuilder` reads nav min/max from each spec, the new
|
||||||
|
`test_navigation_mesh_matches_each_venue_bounds` checks all three meshes, and
|
||||||
|
`GameManager._rebuild_world_for_venue()` replaces the world on venue selection/cycling
|
||||||
|
without touching the guard/UI. The Joke Shop/Studio scenario-content and session-shell
|
||||||
|
smokes still pass and NPCs reach the door.
|
||||||
|
- [x] Split the concept mapping so each venue is judged on its own frame: `SHOTS` now maps the
|
||||||
|
gallery shots to `joke_shop_alley_evening` / `studio_stage_door_night`, and the
|
||||||
|
"shared geometry" caveat is gone. `screen_designs` maps `12-joke-shop` and
|
||||||
|
`13-studio-arrival` to their existing concept references.
|
||||||
|
- [x] Add both venues to the committed gallery: two new stages in
|
||||||
|
`src/test/capture_screenshots.gd` (`12-joke-shop.png`, `13-studio-arrival.png`) reuse the
|
||||||
|
existing `joke_shop_queue` / `studio_arrival` designs. `check_screenshots.sh` is **13 good /
|
||||||
|
0 bad**; the new screenshots are 18.2 / 32.6 MAE against their references.
|
||||||
|
- [-] Update `docs/architecture.md` (venue geometry), `README.md` (gallery) and the screenshot
|
||||||
|
table when the stages land.
|
||||||
@@ -4,6 +4,37 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
|
|
||||||
|
- The visual-alignment path/loader/panel/HUD work and the realism lighting pass are complete and
|
||||||
|
recorded in `DONE.md`; remaining visual items are the realism follow-ups in `TODO.md`.
|
||||||
|
- `PLAN.md` is a compact product/design document, not an implementation log. Historical control
|
||||||
|
and v0.3.0 specs were folded into its Presentation and Controls section; put new step-by-step
|
||||||
|
work in `TODO.md`, not the plan.
|
||||||
|
- Editable Blender sources under `assets/models/blender_src/` are gitignored and untracked
|
||||||
|
(46 `.blend` files removed from the index on 2026-11-01); the `.gdignore` marker stays tracked.
|
||||||
|
- `screen_designs.py judge` default `google/gemini-2.5-flash-lite` worked on 2026-11-01
|
||||||
|
(7 match / 4 review / 0 diff); `--model` remains available.
|
||||||
|
- `capture_realism.gd` parses `--portrait-type=N`; it was documented but ignored before the gear
|
||||||
|
pass. Portrait captures need the requested NPC type to have spawned, otherwise the stage can
|
||||||
|
finish with an empty frame; use the Blender character previews (`render --only npc_coat`) for
|
||||||
|
deterministic gear checks.
|
||||||
|
- Headgear: the firefighter helmet was lifted 2 cm after the brow/face pass; cap and hood were
|
||||||
|
visually checked on `npc_vest`/`npc_hoodie`. The beanie is unused and shares the cap shell.
|
||||||
|
- Gear fit is measured with `python3 tools/blender_assets.py fit` (stdlib, reads the GLBs):
|
||||||
|
head width uses skin vertices weighted to Head in y 1.55–1.81; clothing radius is the cloth
|
||||||
|
skirt in y 0.30–0.52 with a pants hip fallback. The command reports per-character gear boxes,
|
||||||
|
head-band status and x/radial skirt clearance; `test_blender_assets` covers its shape.
|
||||||
|
- Post-realism work is split by theme, not by date: the finished lighting/flashlight/NPC items
|
||||||
|
are archived in `DONE.md`, and `TODO.md` carries three planned workstreams (NPC gear fit,
|
||||||
|
venue geometry + committed gallery, Forward+ desktop preset). New visual work should be added
|
||||||
|
as concrete steps under one of those headings instead of a new "realism" bucket.
|
||||||
|
|
||||||
|
- 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 +44,298 @@ 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`.
|
||||||
|
|
||||||
|
- **Image root (ADR-0014, 2026-11-01):** `assets/images/` is the runtime root for composed
|
||||||
|
interface/scene art (`ui/`, `scenes/`); tiled PBR textures stay under `assets/textures/`;
|
||||||
|
`docs/screens/designs/` and `docs/screens/concepts/` stay reference-only; QA captures stay in
|
||||||
|
`docs/screenshots/`. When a mockup becomes runtime art, copy/process it into `assets/images/`
|
||||||
|
with a new name (no overwrites) and keep a fallback in code.
|
||||||
|
**Mockup copies:** 21 design references live in `assets/images/ui/`; `docs/screens/designs/`
|
||||||
|
remains the reference-only copy. `tools/screen_designs.py` reads/writes the runtime root.
|
||||||
|
|
||||||
## Handoff (read this first)
|
## Handoff (read this first)
|
||||||
|
|
||||||
- Repo: `/run/media/system/DATA/Documents/06-games/security-game`. Godot: `~/.local/bin/godot` (4.3.stable).
|
- **Status (2026-11-01, after the venue-geometry pass):** v0.7.0 is released. Post-v0.6.0 the
|
||||||
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **88/88**.
|
realism pass, the visual-alignment pass and the NPC face/hand pass all landed. Headless
|
||||||
|
**229/229**, Python tooling **87/87**, `tools/check_screenshots.sh` **11 good / 0 bad**,
|
||||||
|
`screen_designs.py compare` 11 good / 0 bad, `scene_concepts.py audit` all GOOD,
|
||||||
|
`blender_assets.py audit` exit 0, `smoke_npc_budget` OK (16 avatars, 6.94 ms of 50 ms).
|
||||||
|
`PLAN.md` is compact; README/architecture track the current controls, asset pipeline, CI and
|
||||||
|
release artifacts. The historical notes below remain useful for implementation gotchas; for
|
||||||
|
current tasks use `TODO.md`.
|
||||||
|
|
||||||
|
- **Phase 2 — AI texturing (this agent, DONE 2026-11-01).** Phase 1 assets were
|
||||||
|
already in the tree (untracked) and are preserved. Generated six seamless AI
|
||||||
|
clothing albedos into `assets/textures/npc/ai/{shirt,trousers,jacket,vest,
|
||||||
|
coat,dress}.png` (512×512, `--role tile`, gpt-5-image-mini; audit 6 good).
|
||||||
|
**Gotcha:** `asset_pipeline.py generate` defaults to `GEN_SIZE=2048`, which
|
||||||
|
gpt-5-image-mini rejects (HTTP 400); pass `--request-size 1024` for 1:1 roles.
|
||||||
|
`generate --role tile` already applies `make_seamless`, so a separate
|
||||||
|
`seamless` pass is redundant. Added to `tools/npc_pipeline.py`: `prepare`
|
||||||
|
(layers AI shirt→cloth, trousers→pants albedos at 128px) and `palette`
|
||||||
|
(writes `assets/textures/npc/palette.json` via `model_pipeline.dominant_colors`).
|
||||||
|
`write_slot_textures()` now skips the albedo for slots with an AI override
|
||||||
|
(normal/ORM stay procedural). Blender `build` re-run: GLB audit ok, 1204 tris /
|
||||||
|
2488 verts, AI albedo embedded for cloth+pants. Phase 2 completed and shipped with
|
||||||
|
the v0.6.0 asset library.
|
||||||
|
**Import concurrency:** a parallel agent is running
|
||||||
|
`timeout 300 godot --headless --editor --quit --path .` and then checks
|
||||||
|
`assets/models/generated/npc_base.glb.import` — treat that as the canonical
|
||||||
|
import. My own `godot --headless --path . --import` hung with no output past
|
||||||
|
the engine banner (two Godot instances contended on `.godot`); I killed the
|
||||||
|
orphan (PID 2482934). Do not run a second import while that `--editor --quit`
|
||||||
|
is active.
|
||||||
|
Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes
|
||||||
|
`palette.json` and the GLB; Phase 2's capture/judge item is blocked on that.
|
||||||
|
|
||||||
|
- **Git hygiene + tooling (2026-11-01):** dev asset folders are now gitignored
|
||||||
|
and untracked: `assets/models/concepts/`, `assets/models/npc/` (Blender
|
||||||
|
sources), `assets/textures/npc/ai/` (raw AI fabrics), and the raw
|
||||||
|
`assets/textures/ai/*` outputs (only `assets/textures/ai/game_ready/` stays).
|
||||||
|
Kept in the repo: `assets/textures/npc/*_{albedo,normal,orm}.png` +
|
||||||
|
`palette.json`, `assets/models/generated/*` (props + npc_base.glb), and the
|
||||||
|
authored `assets/textures/*` set. `tools/asset_pipeline.py generate` gained
|
||||||
|
`--variants N` — generates N alternatives saved as `<stem>-1..N.png` (N=1
|
||||||
|
keeps the unsuffixed path). Python tooling tests still pass (exit 0).
|
||||||
|
|
||||||
|
- **Screenshots + docs (2026-11-01):** regenerated the eleven README captures
|
||||||
|
(`capture_screenshots.gd` + `capture_prop_gallery.gd`) — ten changed, the
|
||||||
|
prop gallery is byte-identical (props unchanged). README gained an NPC
|
||||||
|
character-model subsection, a "Confirmed game assets" section (linked
|
||||||
|
`game_ready` textures, generated models, NPC maps), and refreshed
|
||||||
|
dev-status / not-yet lines; `docs/architecture.md` Level 2 now lists
|
||||||
|
`npc_pipeline.py` + `asset_pipeline.py` and the confirmed-asset locations.
|
||||||
|
|
||||||
|
- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at
|
||||||
|
`/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.3.stable).
|
||||||
|
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**.
|
||||||
|
Python tooling tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs test_model_pipeline` → **53**.
|
||||||
Session shell smoke: `godot --headless --path . --script res://src/test/smoke_session_shell.gd` → exit 0.
|
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.
|
||||||
|
- **Realism pass (2026-11-01, completed + adopted):** `tools/scene_concepts.py`
|
||||||
|
(`list`/`generate`/`process`/`promote`/`audit`/`realism`) never overwrites an image
|
||||||
|
(`next_free_path` adds `-vN`; `process` skips existing unless `--new`; `promote` keeps an
|
||||||
|
existing pick). Raw AI output: `assets/textures/ai/v2/raw/` (gitignored). Candidate pool:
|
||||||
|
`assets/textures/ai/game_ready/v2/` (gitignored) and the **tracked runtime assets** are
|
||||||
|
`.../v2/picks/{skyline_day,skyline_dusk,skyline_night,brick_facade,sidewalk_pavers,asphalt,
|
||||||
|
asphalt_wet,awning_canvas}.png` (+ `_normal`). Concepts `docs/screens/concepts/` (committed,
|
||||||
|
`.gdignore`, targets in its README). Runs `docs/screens/realism/<run>/` (PNG captures
|
||||||
|
gitignored; `report.{json,md}` + `sheet.jpg` committed).
|
||||||
|
`src/test/capture_realism.gd` runs five stages (day 0 s / evening 620 s / late 760 s /
|
||||||
|
joke_shop 700 s / studio 700 s; `--stages=` subset) and swaps textures by the material's
|
||||||
|
*original* albedo path. **Gotcha:** in the SceneTree script, do not overwrite `_phase` after
|
||||||
|
`_begin_stage()` sets `done`, or the run loops forever between stage/shoot (`_queue` empty).
|
||||||
|
**Gotcha:** `elapsed_s = 620` is *Evening* (dusk), 700+ is Late. Godot 4.3
|
||||||
|
`StandardMaterial3D` has no `fog_enabled`; the skyline is a `ShaderMaterial` with
|
||||||
|
`render_mode unshaded, cull_disabled, fog_disabled` + `ALPHA_SCISSOR_THRESHOLD` so the
|
||||||
|
panorama's own haze shows. Backdrop plane is 90 m tall with 2 UV repeats (~200×90 m per
|
||||||
|
repeat, near the 2:1 panorama aspect). Legacy `assets/textures/skyline.png` is no longer
|
||||||
|
loaded but stays on disk for the `test_world` asset check.
|
||||||
|
**Phase look:** day sun 0.62 / exposure 0.6 / ambient 0.22; evening sun 0.12 / exposure
|
||||||
|
0.72 / dark violet sky; late exposure 0.95 / ambient 0.10 with the wet asphalt street and a
|
||||||
|
bright skyline tint (1.5) so the night windows read. Stripes: `stripe_period()` (detrended
|
||||||
|
autocorrelation) + `align_stripes()` (resample to whole periods) replace the edge crossfade
|
||||||
|
for `awning_canvas`; `audit_striped_tile()` compares the wrap step with the harshest interior
|
||||||
|
step.
|
||||||
|
**Judge:** the default `google/gemini-2.5-flash-lite` mostly works but intermittently answers
|
||||||
|
HTTP 400 "API key not valid" (one shot of ten on 2026-11-01); re-run or pass
|
||||||
|
`--model google/gemini-2.5-flash` when it does. Scores are noisy (±1–2), so
|
||||||
|
`realism --repeats 3` averages and `summarize()` reports a like-for-like mean over the shots
|
||||||
|
every set captured. Final: overall 0.8→1.0, backdrop 0.6→1.6, metric gap 0.444→0.348.
|
||||||
|
`screen_designs.py judge` gained one retry for the same transient 400.
|
||||||
|
**Character portrait capture (2026-11-01):** `capture_realism.gd` has a `portrait` stage
|
||||||
|
(`npc_portrait` -> concept `npc_portrait_day`) because crowd shots cannot judge avatars. It
|
||||||
|
freezes one NPC, parks the rest of the crowd 140 m away (a passer-by otherwise blocks the
|
||||||
|
frame), moves the subject to an open sidewalk patch, turns it into the camera and stands the
|
||||||
|
guard on the sunlit side. Needed because the NPC FSM re-writes `rotation.y` every physics tick
|
||||||
|
(so the yaw must be set after `set_physics_process(false)`), the venue's morning sun leaves a
|
||||||
|
face in shadow from most angles, and `visible = false` does not keep the crowd out of frame.
|
||||||
|
`--portrait-type=N` forces a role; hooded roles (trespasser/violent/crisis) are skipped.
|
||||||
|
**Flashlight (2026-11-01, final):** a *tight* `SpotLight3D` on `Head`: `spot_range` 20,
|
||||||
|
`spot_angle` 14, `light_energy` 4.2, `spot_attenuation` 0.9, forward `rotation_degrees = 0`,
|
||||||
|
starts invisible. A fake additive light shaft was tried twice (cylinder with a UV ramp, then a
|
||||||
|
vertical-fade `ShaderMaterial`) and **rejected both times**: on GL Compatibility a first-person
|
||||||
|
camera inside the cone renders it as a filled disc and the far ring shows, so
|
||||||
|
`Materials.light_beam()` and the beam mesh are gone. Regression guard:
|
||||||
|
`test_flashlight_aims_a_tight_forward_spot` in `src/test/tests/test_door_and_flashlight.gd`
|
||||||
|
(lamp on the head, aims straight, cone ≤ 20°, starts off). Night captures are staged in LATE
|
||||||
|
(`elapsed_s` 760), not dusk.
|
||||||
|
**NPC detail pass (2026-11-01):** `_face()` now builds sclera + iris + upper lid per eye, brow
|
||||||
|
ridge, cheekbones, nose bridge/tip, ears with lobes and split upper/lower lips; the new
|
||||||
|
`eye_white` material is a *fixed-colour* slot, so `_apply_outfit_materials()` leaves it white.
|
||||||
|
`_hands_and_shoes()` has four fingers plus a thumb and `_clothing()` adds `Cuff` tori.
|
||||||
|
Budget work: `CHAR_MAX_VERTS` 5000 (see the library note), body decimation 0.26, upper lids are
|
||||||
|
boxes rather than spheres, and knuckle spheres/nostrils were cut to stay inside it.
|
||||||
|
**Orientation debugging:** to check a GLB's front axis, compare the mean Z of the `eye_white`
|
||||||
|
surface (face) and of the `rubber` sole verts (toes) — all 8 avatars are +Z, so any
|
||||||
|
"they walk backwards" symptom is a yaw bug, not an export flip.
|
||||||
|
- **Blender asset library (v0.6.0, integrated 2026-11-01):**
|
||||||
|
`tools/blender_assets.py` (`build`/`render` in Blender; `audit`/`sheet`/`list` stdlib) writes
|
||||||
|
`assets/models/blender/{npc,npc_gear,props}/*.glb`, `manifest.json` (integration source of
|
||||||
|
truth), sources in `assets/models/blender_src/` (`.gdignore`d), and review sheets
|
||||||
|
`docs/blender_previews/sheet_*.jpg` (per-asset PNG renders are gitignored). 8 characters, 18
|
||||||
|
gear, 20 props; notes in `docs/blender-assets.md`. Characters are drop-in for `npc_base`
|
||||||
|
(same mesh node name, bones at the same rest positions, five tint slots) and stay under the
|
||||||
|
6000/4000 budget only because of body decimation (0.26) + `smart_project` at 80°: glTF vertex
|
||||||
|
count includes UV-seam splits, so extra beveled detail quickly breaks the limit. The 2026-11-01
|
||||||
|
face/hand pass raised `CHAR_MAX_VERTS` to 5000 (tris 3,974–5,026) after measuring 16 avatars at
|
||||||
|
~6.9 ms of a 50 ms frame; `npc_pipeline.MAX_VERTICES`/`MAX_TRIANGLES` (base mesh) are unchanged.
|
||||||
|
**Gotchas:** `bisect_plane` and thin bevels leave zero-area triangles (Godot warns "non-finite
|
||||||
|
normal in LOD generation"); `_clean()` + a weld after bisect fix it, and the audit rejects them.
|
||||||
|
Fixed-colour detail materials carry a 4×4 texture because `_apply_outfit_materials()` sets
|
||||||
|
`albedo_color = WHITE` on unknown slots. Cycles CPU render ≈ 3 s/asset; Godot import of the
|
||||||
|
library is clean.
|
||||||
|
**Integration:** `NpcAvatar.character_model_for()` selects a variant by role/seed; only the
|
||||||
|
five contract slots are tinted (detail materials untouched; `npc_vest` keeps its fixed hivis
|
||||||
|
vest, firefighter/guard get dark uniform tints). Gear is attached at head/back/chest/hand and
|
||||||
|
falls back to primitives; `npc_hoodie` skips the extra hood gear. `WorldBuilder` prefers
|
||||||
|
`assets/models/blender/props/` for the six replacements and the new street dressing, fits the
|
||||||
|
same colliders/obstacles, and collects library emissives for `set_night_mode()`.
|
||||||
|
`LIBRARY_TRIANGLE_BUDGET` = 60k (current ~27,986). Library props are excluded from
|
||||||
|
`PROP_PLACEMENTS` only when they need dedicated slots (door/marquee/sedan/handrail/lantern).
|
||||||
|
**Gear fit (v0.6.1):** `fit` measures head width (0.315 m), skirt/hip radius and gear bounds;
|
||||||
|
sunglasses are 0.202 m, the bag's body inner face is 0.262 m (coat 0.225, dress 0.260), and the
|
||||||
|
helmet is lifted 2 cm above the brow. `test_npc_character` guards head/back gear in Godot.
|
||||||
|
Blender's GLB export is byte-nondeterministic for unchanged assets (padding/index order), so do
|
||||||
|
not commit a full no-op rebuild; rebuild only the assets whose geometry changed.
|
||||||
|
- **Venue geometry (v0.6.1+):** `WorldBuilder.VENUE_SPECS` is the per-venue table; `WorldBuilder.new(root, id)` / `set_venue()` select it, unknown ids fall back to `hollywood_building`. Dimensions, window/pilaster rows, stairs, street, nav bounds, guard spawn, feature flags and prop placements are read from the spec. `GameManager.world_root` holds only the world; `_rebuild_world_for_venue()` frees/recreates it on venue selection/cycling so guard/UI survive. Joke Shop uses procedural alley walls/shutters/dumpster/AC; TV Studio uses a stone facade, bulb canopy, car paint and barrier placements. Gallery shots 12/13 use the existing `joke_shop_queue`/`studio_arrival` references.
|
||||||
|
- **Screen-design judge (2026-11-01):** `screen_designs.py judge` could not run because `OPENROUTER_API_KEY` returned HTTP 400 "API key not valid". `check_screenshots.sh` still passed 13/13; the judge retry is a TODO follow-up.
|
||||||
|
- **Portrait capture:** `capture_realism.gd` faces the **Rig** toward the camera (using the
|
||||||
|
inverse look direction) and hides the blue `FocusCircle`; the old code used the wrong yaw and
|
||||||
|
left the focus disc over the face. An explicit `--portrait-type=N` now overrides the hooded
|
||||||
|
skip, but the day roster spawns none of types 5-7, so a hooded close-up is deferred.
|
||||||
|
- **v0.6.0 environment pass:** `tools/bake_ao.py` bakes 256² AO maps per library asset
|
||||||
|
(`<name>_ao.png`, red channel) and `Materials.apply_ao()`/`SurfaceMaterials` wire them in;
|
||||||
|
`tools/make_normal_maps.py` derives tangent normals for the 1024² brick/concrete/asphalt
|
||||||
|
textures. Forward+ boots on desktop but is rejected for v0.6.0 (project-wide renderer, Web
|
||||||
|
must stay GL Compatibility). Headless 222/222, Python 69/69.
|
||||||
|
- **Prop pipeline (done):** audit enforces 400 tris / 600 verts, ground contact, UV range, no
|
||||||
|
degenerate tris, one material. `capture_prop_gallery.gd` writes `10-prop-gallery.png` (3x2 studio
|
||||||
|
line-up, back row on a platform) and the `prop_gallery` catalogue entry compares it.
|
||||||
|
- **NPC character pass (v0.5.0):** Rig is articulated (`Rig/Hip* -> Knee`, `Shoulder -> Elbow ->
|
||||||
|
Hand`); `outfit_for` adds layer/hair_style/hand_item; `_idle_layer` drives phone/clipboard/crossed
|
||||||
|
and escalation gestures; `_reaction_tag` shows `!!`, `?!`, `!`, `?`; `apply_lod` swaps to a
|
||||||
|
billboard impostor at 26 m (hysteresis 1.5 m); GameManager `_refresh_queue` assigns queue offsets.
|
||||||
|
Perf: 16 avatars = 534 meshes, ~6.9 ms headless.
|
||||||
|
- **Player movement (v0.5.0):** **W was inverted** (`get_axis(forward, back)` is negative for W and
|
||||||
|
the code multiplied it directly); now `forward * -input_dir.y`. Step-up uses test_move + a raised
|
||||||
|
copy (0.32 m). Guard emits footstep/landed/precision_changed; GameManager forwards them to
|
||||||
|
AudioFeedback and the HUD. Gamepad actions are added at runtime by
|
||||||
|
`GuardController._ensure_gamepad_bindings`; remaps persist via `Save` `input_bindings` and are
|
||||||
|
applied by `InputBindings.apply` (key/button events only). Windowed `smoke_movement.gd` guards it.
|
||||||
|
- **Scheduled TODO pass complete:** only the optional Future ideas remain (live replay, extra
|
||||||
|
venues, voice audio, image-to-mesh). Grounding facts: the
|
||||||
|
NPC rig is rigid capsules with a whole-limb swing, and `guard_controller.gd` uses one `accel`
|
||||||
|
value. Each task should get its own windowed smoke or capture where it changes feel/looks.
|
||||||
|
- **Backlog complete:** every planned TODO section is done and moved to `DONE.md`; `TODO.md`
|
||||||
|
now only lists optional future ideas (live 3D replay, NPC LOD, more venues, real audio, prop
|
||||||
|
atlas, AI mockups for the new catalogue entries, image-to-mesh props). GUT was consciously not
|
||||||
|
adopted (custom runner covers it without an editor plugin).
|
||||||
- Do not re-open `override_access`. Do not bind wand to `W`. Do not remove the radio easter egg.
|
- Do not re-open `override_access`. Do not bind wand to `W`. Do not remove the radio easter egg.
|
||||||
|
|
||||||
|
## UI / Presentation Decisions
|
||||||
|
|
||||||
|
- Runtime UI/scene art is loaded through `src/game/ui/art.gd` (`assets/images/ui/` then `scenes/`); missing files return null so panels keep procedural fallbacks.
|
||||||
|
- Screen mockups with baked-in controls are used as backdrops plus an opaque `ArtPanel` cover, not as clickable full-screen skins.
|
||||||
|
- Radio, wand, inspection, and body-camera timeline panels use their reference art as backdrops; forms sit on transparent, padded panels over the covered mockup controls.
|
||||||
|
- HUD target names use a boxed chip under the crosshair; escalation has a five-segment bar below the text label. The body-camera REC/clock block starts at y=92 on the right so it clears the shift/rep/crowd text.
|
||||||
|
- Staged captures in `docs/screenshots/` are committed and were last regenerated after the realism and NPC passes; re-run `tools/check_screenshots.sh` (not a partial capture) before replacing them.
|
||||||
|
- `tools/check_screenshots.sh` is the repeatable capture+compare smoke; both capture scripts accept `--out=res://docs/screenshots`. Judge verdicts on the staged set were 7 match / 4 review / 0 diff.
|
||||||
|
- Venue selection used to leave `guard.input_locked` true and the mouse visible; `_start_shift()` must call `_refresh_freeze()` after starting the clock.
|
||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
|
- **v0.3.0 pass (2026-10-31, historical; shipped):** rendering, generated prop models, screen
|
||||||
|
catalogue, aim-free selection. See ADR-0011 (selection) and ADR-0012 (materials/props).
|
||||||
|
- `src/game/materials.gd` (`SurfaceMaterials`) is now the single source of surface
|
||||||
|
recipes + `configure_environment()`. `GameManager.PHASE_LIGHTING` gained
|
||||||
|
`exposure/glow/fog_color/fog_density/fog_energy/contrast/saturation/ambient_energy`.
|
||||||
|
Renderer is **GL Compatibility**, so SSAO/SSIL are unavailable; glow+fog+ACES are used
|
||||||
|
instead and unsupported settings are ignored rather than gated in code.
|
||||||
|
- World: scaled brick (uv 22,10), framed windows with per-window night emission
|
||||||
|
(`i % 3 != 2` lit), stone base course, door trim/handles, curb, lane dashes (the old
|
||||||
|
loop had `Vector3(i * 0.0, …)` and stacked every dash at x=0), street lamps use the
|
||||||
|
metal profile. The two flat planters that clipped the steps were removed.
|
||||||
|
- `tools/model_pipeline.py` builds `assets/models/generated/*.glb` (bollard, bench,
|
||||||
|
trash_bin, planter_box, newspaper_box, fire_hydrant) from box/cylinder specs;
|
||||||
|
`WorldBuilder._add_prop_model()` loads them with a procedural fallback and records
|
||||||
|
`loaded_models` / `fallback_props`. **Gotcha:** glTF `baseColorFactor` is LINEAR —
|
||||||
|
writing sRGB made every prop pastel; `srgb_to_linear()` is applied in `build_glb`.
|
||||||
|
**Gotcha:** a new/changed `.glb` needs `godot --headless --path . --import` before
|
||||||
|
`ResourceLoader.exists()` / `load()` sees it (test_generated_props fails otherwise).
|
||||||
|
- `model_pipeline.py concept` (OpenRouter, reads repo `.env`) writes per-prop concept
|
||||||
|
sheets to `assets/models/concepts/`; `dominant_colors()` centre-crops and weights by
|
||||||
|
saturation (a grey studio backdrop otherwise wins), `assign_roles()` maps colours by
|
||||||
|
hue/value. Delete a concept file to fall back to the catalogue palette.
|
||||||
|
- NPC avatars: `outfit_for(type, seed)` (pure) gives shirt/trousers/hair/skin +
|
||||||
|
accessory (cap, hood, backpack, sunglasses, lanyard) and height scale; `_arms` and
|
||||||
|
`_legs` now swing (`limb_swing`) while walking. Keep `Rig/Head` named for focus.
|
||||||
|
- Selection: `NpcFocus.rank_indices` (angle then distance) — `pick_index` = first.
|
||||||
|
`GuardController.cycle_target(direction)` walks nearest-first `nearby_candidates()` and
|
||||||
|
sets `target_pinned`, which makes `_update_target()` skip the cone. `clear_target()`
|
||||||
|
sets `focus_suppress_s = 0.75` so the cone does not instantly re-acquire the person.
|
||||||
|
New input actions `walk_slow` (Shift), `cycle_target_next` / `cycle_target_prev`
|
||||||
|
(wheel down / up). Right click with nothing open clears focus (Esc still pauses).
|
||||||
|
- HUD: `nearby_label` + `set_nearby_hint(count)` show `hud.nearby`; `hud.controls` and
|
||||||
|
`hud.targeting` mention the wheel and Shift.
|
||||||
|
- `tools/screen_designs.py`: catalogue now 19 screens (added wand_screening,
|
||||||
|
joke_shop_queue, studio_arrival, crowd_panic, responder_arrival,
|
||||||
|
body_camera_timeline, escalation_hud), new `list` command, `generate --only/--force`
|
||||||
|
(existing designs are kept by default so a draft run cannot clobber AI mockups),
|
||||||
|
and `judge --dry-run` no longer raises `KeyError: 'class'`.
|
||||||
|
- Screenshot tool leak: `capture_screenshots.gd` intermittently prints
|
||||||
|
`ObjectDB instances leaked at exit`. Verified against a `git worktree` of HEAD~ — the
|
||||||
|
baseline leaks more often (3/3 vs 1/3), so it is a pre-existing quit-timing artifact,
|
||||||
|
not a regression. Normal boots are clean.
|
||||||
|
|
||||||
- Access artifacts: `src/core/access_loadout.gd` builds from event `override_access` + venue policy.
|
- 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
|
||||||
@@ -33,9 +343,9 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
`carried_items` per spawn (seeded). Tests in `src/test/tests/test_access_loadout.gd`.
|
`carried_items` per spawn (seeded). Tests in `src/test/tests/test_access_loadout.gd`.
|
||||||
- Wand shortcut is **`C`**, not `W` (`W` is walk). `radio_emergency` input map no longer includes W.
|
- Wand shortcut is **`C`**, not `W` (`W` is walk). `radio_emergency` input map no longer includes W.
|
||||||
`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 (historical; smoke-tested): cone focus via `src/game/npc_focus.gd`,
|
||||||
accel/decel, gravity only airborne, `input_locked` freeze, Esc no longer on the guard.
|
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),
|
||||||
@@ -45,7 +355,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
awning, and marquee are visible; step treads rise toward the door; `ShiftCoordinator` pulls the
|
awning, and marquee are visible; step treads rise toward the door; `ShiftCoordinator` pulls the
|
||||||
event scheduler once per simulated second (was every physics frame → >1200 NPCs in seconds);
|
event scheduler once per simulated second (was every physics frame → >1200 NPCs in seconds);
|
||||||
`GameManager.MAX_LIVE_NPCS = 16` caps live avatars.
|
`GameManager.MAX_LIVE_NPCS = 16` caps live avatars.
|
||||||
- **Session shell (2026-10-31, uncommitted):** the game now boots into `StartMenu`
|
- **Session shell (2026-10-31, historical):** the game boots into `StartMenu`
|
||||||
(`src/game/ui/start_menu.gd`, layer 20) instead of a live shift. `GameManager._on_venue_selected()`
|
(`src/game/ui/start_menu.gd`, layer 20) instead of a live shift. `GameManager._on_venue_selected()`
|
||||||
builds the scenario and calls `_start_shift()`; `_shift_running` gates input and `_refresh_freeze()`.
|
builds the scenario and calls `_start_shift()`; `_shift_running` gates input and `_refresh_freeze()`.
|
||||||
Esc with nothing open now pauses (`PauseOverlay`, layer 18) → resume / restart / settings /
|
Esc with nothing open now pauses (`PauseOverlay`, layer 18) → resume / restart / settings /
|
||||||
@@ -58,9 +368,9 @@ 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, historical):** the radial 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`).
|
||||||
`eye_contact` reuses the Level 1 reaction table via `_apply_interaction`. Recognition is a player
|
`eye_contact` reuses the Level 1 reaction table via `_apply_interaction`. Recognition is a player
|
||||||
decision: `NPCController.player_recognized` + `IncidentRecorder.add_access_evidence`, and
|
decision: `NPCController.player_recognized` + `IncidentRecorder.add_access_evidence`, and
|
||||||
@@ -94,6 +404,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 +468,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 +597,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).
|
||||||
@@ -223,9 +618,9 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
- Persistence tests write into the real `user://shift_results.json` (harmless local pollution of the demo history).
|
- Persistence tests write into the real `user://shift_results.json` (harmless local pollution of the demo history).
|
||||||
- macOS export required `rendering/textures/vram_compression/import_etc2_astc=true` in `project.godot` (universal build demands ETC2 ASTC enabled).
|
- macOS export required `rendering/textures/vram_compression/import_etc2_astc=true` in `project.godot` (universal build demands ETC2 ASTC enabled).
|
||||||
- Joke Shop `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI.
|
- Joke Shop `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI.
|
||||||
- Access loadout is implemented in the working tree (see Current State); do not re-fix `override_access` as if it were still unread.
|
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
|
||||||
- `Save.SETTINGS_FILE` is declared and unused; no mouse-sensitivity or pause menu yet.
|
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
|
||||||
- Headless suite is **77/77**; the windowed focus/radial smoke passes. Remaining visual follow-ups are at the top of `TODO.md`.
|
- Headless suite is **229/229**; the windowed smokes, screenshot compare and judge pass. Remaining visual follow-ups are in `TODO.md`.
|
||||||
- Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args).
|
- Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args).
|
||||||
- GDScript inference trap that broke the main scene: a `var` typed as a base class
|
- GDScript inference trap that broke the main scene: a `var` typed as a base class
|
||||||
(`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a
|
(`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a
|
||||||
@@ -240,23 +635,23 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
- Add a vision-model (LLM) judge on top of the screen-design comparison: pixel and
|
|
||||||
perceptual metrics cannot decide whether a screenshot's composition is correct.
|
|
||||||
- Whether the responder avatar should be playable/controllable or purely scripted.
|
- Whether the responder avatar should be playable/controllable or purely scripted.
|
||||||
- Localization target languages beyond English.
|
- Localization target languages beyond English.
|
||||||
- macOS notarization approach for broad distribution.
|
- macOS notarization approach for broad distribution.
|
||||||
|
|
||||||
## Plan vs code (re-evaluated)
|
## Plan vs code
|
||||||
|
|
||||||
On local `main` (6 commits not on origin): v0.2.1 packages, ADRs 0007–0010, asset pipeline wired to
|
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
|
||||||
`assets/textures/ai/game_ready/`, screenshots refreshed.
|
2026-11-01 the playable build covers all three venues, controls, access/radio/evidence/review,
|
||||||
|
the Blender asset library, the realism pass, and the visual-alignment pass. `TODO.md` holds only
|
||||||
Uncommitted: `AccessLoadout`, control pass (cone, ring, radial, NPC face, named input actions),
|
the realism follow-ups and optional future ideas. The project uses a custom headless runner, not
|
||||||
and boot/spawn fixes. Headless 77/77; windowed smoke passes. Access-artifact and control-pass
|
GUT, and Gitea Actions, not GitHub Actions.
|
||||||
sections moved to `DONE.md`.
|
|
||||||
|
|
||||||
Still in `TODO.md`: world follow-ups (guard float, step colliders, skyline asset, marquee size);
|
|
||||||
session shell; leftover PLAN verbs; crowd rubberneck; emergency aftermath; body-cam replay;
|
|
||||||
sit/photo/film poses; real crowd audio; Joke Shop/Studio extra events; PLAN.md says GUT/GitHub.
|
|
||||||
|
|
||||||
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|
||||||
|
|
||||||
|
## Uncommitted Workstreams
|
||||||
|
|
||||||
|
- **None (2026-11-01).** The NPC detail pass, the realism follow-ups and the flashlight beam
|
||||||
|
decision are committed together with the portrait capture and the refreshed screenshots.
|
||||||
|
Keep the rule from `AGENTS.md`: check `git status` before committing, and never revert
|
||||||
|
another contributor's in-flight files.
|
||||||
@@ -1,599 +1,187 @@
|
|||||||
# Game Plan: Step Line
|
# Game Plan: Gig Line
|
||||||
|
|
||||||
## Premise
|
## Premise
|
||||||
|
|
||||||
You play a private security guard stationed at the top of the steps outside a fictional building on Hollywood Boulevard. Your job is to keep the entrance and steps clear while dealing with tourists, trespassers, performers, protesters, intoxicated pedestrians, and occasional determined intruders.
|
You are a private security guard working the entrance of a fictional Hollywood Boulevard building. You keep the steps and doors clear, check who is allowed in, and deal with tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in mental-health crisis, and rare determined threats.
|
||||||
|
|
||||||
The game is about **judgment, communication, and escalation management**, not simply attacking anyone who approaches.
|
The game is about **judgment, communication, access verification, and proportionate escalation**, not about attacking everyone who approaches.
|
||||||
|
|
||||||
---
|
## Design Pillars
|
||||||
|
|
||||||
|
- **Lowest effective response wins.** Observation, a gesture, or a clear direction should solve most situations.
|
||||||
|
- **People are not labels.** Clothes, props, credentials, and behavior tell the player what they need; internal NPC types stay in the debug overlay.
|
||||||
|
- **Every decision has a record.** Incidents, evidence, radio reports, and outcomes feed the shift review.
|
||||||
|
- **Emergency help is a last resort.** Responders handle crises the guard cannot safely manage; calling them has costs.
|
||||||
|
- **Readability beats raw fidelity.** The GL Compatibility renderer, clear silhouettes, the focus ring, and the HUD stay legible.
|
||||||
|
|
||||||
## Core Gameplay Loop
|
## Core Gameplay Loop
|
||||||
|
|
||||||
1. Watch the stairs, entrance, and sidewalk.
|
1. Watch the entrance, steps, queue, and sidewalk.
|
||||||
2. Identify why each person is approaching.
|
2. Work out why each person is approaching.
|
||||||
3. Give verbal warnings and directions.
|
3. Use a gesture or short direction before escalating.
|
||||||
4. Escalate only when necessary.
|
4. Verify authorized access when the venue requires it.
|
||||||
5. Call for assistance in rare dangerous situations.
|
5. Escalate only when the person refuses, forces entry, or presents a genuine threat.
|
||||||
6. Maintain public safety, building security, and your employer’s reputation.
|
6. Record the incident and call for help when it is justified.
|
||||||
|
7. Review the shift: safety, judgment, accuracy, crowd reaction, and reputation.
|
||||||
|
|
||||||
People should not all behave the same way. Some are confused, some are rude, some are filming, and a very small number are deliberately attempting to enter.
|
A shift lasts roughly 10–15 minutes. Most people comply when approached calmly.
|
||||||
|
|
||||||
## Authorized Access
|
## Access and Verification
|
||||||
|
|
||||||
Some people are authorized to enter the building. The security guard must verify them before granting access.
|
Some people are authorized to enter. The guard may admit them by:
|
||||||
|
|
||||||
An authorized person can be admitted if either:
|
- recognizing a person the guard already knows; or
|
||||||
|
- verifying a venue-appropriate artifact: wristband, ID, ticket, credential, or guest list.
|
||||||
|
|
||||||
- The guard already knows and recognizes them.
|
Artifacts can be valid, expired, counterfeit, mismatched, missing, or ambiguous. The player inspects the details the venue asks for instead of judging by appearance.
|
||||||
- The person presents a valid event or staff wristband, which the guard checks before opening the entrance.
|
|
||||||
|
|
||||||
The wristband check should require the player to inspect details such as its color, pattern, date, or event marking. NPCs may have valid wristbands, expired wristbands, counterfeit wristbands, or no wristband at all.
|
Decisions are admit, redirect, hold, or refuse. Incorrectly denying an authorized person hurts reputation; admitting an unauthorized person is a security failure. Recognition is a player decision and is recorded as evidence.
|
||||||
|
|
||||||
The player should avoid relying on appearance or assumptions. A familiar person may still need verification if the building’s current policy requires it, while an unfamiliar person with a valid wristband may be admitted after a successful check. Incorrectly denying an authorized person harms the building’s reputation; admitting someone without authorization creates a security failure.
|
## Escalation Model
|
||||||
|
|
||||||
---
|
| Level | Player response | Intent |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 0 | Observation | Watch harmless behavior; intervene only when needed. |
|
||||||
|
| 1 | Simple deterrence / polite direction | Raise a hand, make eye contact, point to the sidewalk or check-in area, shake the head, give a short instruction. |
|
||||||
|
| 2 | Formal warning | State the rule and ask for compliance; optionally arm the body camera. |
|
||||||
|
| 3 | Blocking and positioning | Stand between the person and the door, secure the entrance, keep distance and an escape route, ask for their reason. |
|
||||||
|
| 4 | Radio for security assistance | Request a second guard, supervisor, building management, or other support. |
|
||||||
|
| 5 | Emergency response | Hold and confirm the emergency call only for an immediate threat such as a weapon, assault, forced entry, or serious threat to occupants. |
|
||||||
|
|
||||||
## Escalation System
|
At Level 2 an NPC may leave, argue, pretend not to understand, record the guard, call someone, or move closer. At Level 3 the goal is to deny access **without starting a fight**. Physical contact creates legal, employment, and public-relations consequences.
|
||||||
|
|
||||||
## Level 0: Observation
|
At Level 4 the report matters: location, description, behavior, weapons seen, and immediate danger. Exaggerated reports damage credibility. At Level 5 the call may evacuate the building, close the street, trigger a review, cost reputation, or open an investigation if unjustified. Responders may command, restrain, evacuate, or arrest; they are not a routine stronger attack.
|
||||||
|
|
||||||
The guard watches NPC behavior and decides whether intervention is needed.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- Someone taking a photograph from the sidewalk
|
|
||||||
- A tourist reading a map
|
|
||||||
- A person sitting briefly on the edge of the steps
|
|
||||||
- Someone approaching the entrance with a delivery package
|
|
||||||
|
|
||||||
The player should avoid interrupting harmless behavior. Unnecessary confrontations reduce the guard’s performance rating.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Level 1: Simple Deterrence and Polite Direction
|
|
||||||
|
|
||||||
The player can begin with a simple, nonverbal deterrent before speaking. These actions are quick and low-pressure, making them useful when someone is only approaching the steps casually or appears unsure.
|
|
||||||
|
|
||||||
Possible actions include:
|
|
||||||
|
|
||||||
- Raise an open hand to signal “stop” or “please wait”
|
|
||||||
- Make eye contact
|
|
||||||
- Point toward the public sidewalk
|
|
||||||
- Gesture toward the wristband-check area
|
|
||||||
- Shake the head gently when someone approaches a restricted entrance
|
|
||||||
|
|
||||||
If the person continues, the player uses a short verbal prompt.
|
|
||||||
|
|
||||||
Example commands:
|
|
||||||
|
|
||||||
- “Please keep the stairs clear.”
|
|
||||||
- “The entrance is for authorized guests only.”
|
|
||||||
- “You can’t remain here.”
|
|
||||||
- “Please move back to the sidewalk.”
|
|
||||||
|
|
||||||
At this stage, most NPCs comply immediately.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Level 2: Formal Warning
|
|
||||||
|
|
||||||
If the person remains on the steps or continues toward the entrance, the player issues a formal warning.
|
|
||||||
|
|
||||||
Possible dialogue:
|
|
||||||
|
|
||||||
> “This is private property. You have been asked to leave. Please move away from the entrance now.”
|
|
||||||
|
|
||||||
The player may activate a visible security light or body camera. This creates a record of the interaction and can discourage repeat offenders.
|
|
||||||
|
|
||||||
At this level, the NPC may:
|
|
||||||
|
|
||||||
- Leave
|
|
||||||
- Argue
|
|
||||||
- Pretend not to understand
|
|
||||||
- Start recording the guard
|
|
||||||
- Call someone else
|
|
||||||
- Move closer to the door
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Level 3: Blocking and Physical Positioning
|
|
||||||
|
|
||||||
The guard moves into a defensive position between the NPC and the building.
|
|
||||||
|
|
||||||
Gameplay actions:
|
|
||||||
|
|
||||||
- Stand in front of the entrance
|
|
||||||
- Point toward the public sidewalk
|
|
||||||
- Lock or secure the door
|
|
||||||
- Request the person’s reason for entry
|
|
||||||
- Keep distance while maintaining a clear escape route
|
|
||||||
- Use a flashlight or radio warning tone at night
|
|
||||||
|
|
||||||
The player should not automatically shove or strike people. Physical contact creates legal, employment, and public-relations consequences.
|
|
||||||
|
|
||||||
This level is about **denying access without creating a fight**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Level 4: Radio for Security Assistance
|
|
||||||
|
|
||||||
If the person refuses to leave, attempts to force the door, threatens staff, or repeatedly returns, the player uses the radio.
|
|
||||||
|
|
||||||
Possible assistance:
|
|
||||||
|
|
||||||
- A second security guard
|
|
||||||
- Building management
|
|
||||||
- Police dispatch
|
|
||||||
- Nearby security personnel
|
|
||||||
|
|
||||||
The radio call takes time, so the player must continue monitoring the person while waiting. The player can choose what information to report:
|
|
||||||
|
|
||||||
- Location
|
|
||||||
- Description
|
|
||||||
- Behavior
|
|
||||||
- Whether weapons have been seen
|
|
||||||
- Whether anyone is in immediate danger
|
|
||||||
|
|
||||||
Incorrect or exaggerated reports damage the guard’s credibility.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Level 5: Emergency Response
|
|
||||||
|
|
||||||
This is reserved for immediate threats, such as:
|
|
||||||
|
|
||||||
- A person displaying a weapon
|
|
||||||
- An attempted forced entry while people are inside
|
|
||||||
- An assault on the guard or another person
|
|
||||||
- A serious threat against occupants
|
|
||||||
- A violent struggle that cannot be safely disengaged
|
|
||||||
|
|
||||||
The player presses and holds a clearly marked **Emergency Radio** button. A confirmation prompt prevents accidental activation.
|
|
||||||
|
|
||||||
The call brings an off-duty law-enforcement officer or armed emergency responder to the location. This character has greater authority and can control a dangerous situation, but the call has serious consequences:
|
|
||||||
|
|
||||||
- The building may be evacuated
|
|
||||||
- The street may be closed
|
|
||||||
- The guard’s actions are reviewed
|
|
||||||
- The building loses reputation
|
|
||||||
- The player may face an investigation if the call was unjustified
|
|
||||||
- The officer may use force only when the game’s threat assessment supports it
|
|
||||||
|
|
||||||
The officer should not function as a convenient “stronger attack.” Their role is to handle a crisis the player cannot safely manage. In most scenarios, their arrival results in commands, restraint, evacuation, or arrest rather than automatic gunfire.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## NPC Behavior Types
|
## NPC Behavior Types
|
||||||
|
|
||||||
## Tourists
|
- **Tourists:** take photos, sit on steps, drift toward restricted doors; usually respond well to polite directions.
|
||||||
|
- **Street performers:** may block stairs or argue when moved; can be redirected to a permitted area.
|
||||||
- Take photos
|
- **Influencers and filmmakers:** film the guard, test boundaries, and create public-relations risk.
|
||||||
- Sit on steps
|
- **Confused visitors:** may have appointments or genuinely believe the building is public; reward patient explanation.
|
||||||
- Wander toward restricted doors
|
- **Intoxicated pedestrians:** move unpredictably, ignore instructions, and need extra distance or assistance.
|
||||||
- Usually respond well to polite directions
|
- **Trespassers:** test entrances, use distractions, and may return after being told to leave.
|
||||||
|
- **Violent threats:** rare, escalate quickly, and justify emergency response only when behavior shows immediate danger.
|
||||||
## Street Performers
|
- **People in mental-health crisis:** may loiter, behave unpredictably, and respond poorly to aggression; require patience and careful de-escalation. The setting may refer to a 51/50 hold, but the game should present the person respectfully, not as an automatic threat.
|
||||||
|
|
||||||
- May block stairs
|
|
||||||
- May become argumentative if moved
|
|
||||||
- Can be redirected to a permitted area
|
|
||||||
|
|
||||||
## Influencers and Filmmakers
|
|
||||||
|
|
||||||
- Film the guard
|
|
||||||
- Test boundaries for content
|
|
||||||
- May accuse the guard of overreacting
|
|
||||||
- Create public-relations risk
|
|
||||||
|
|
||||||
## Confused Visitors
|
|
||||||
|
|
||||||
- Have appointments or directions
|
|
||||||
- May genuinely believe the building is public
|
|
||||||
- Reward patient explanations
|
|
||||||
|
|
||||||
## Intoxicated Pedestrians
|
|
||||||
|
|
||||||
- Move unpredictably
|
|
||||||
- May ignore instructions
|
|
||||||
- Require additional distance and assistance
|
|
||||||
|
|
||||||
## Trespassers
|
|
||||||
|
|
||||||
- Repeatedly test entrances
|
|
||||||
- Use distractions
|
|
||||||
- May return after being told to leave
|
|
||||||
|
|
||||||
## Violent Threats
|
|
||||||
|
|
||||||
- Rare
|
|
||||||
- Escalate quickly
|
|
||||||
- Trigger the emergency response system only when their behavior indicates immediate danger
|
|
||||||
|
|
||||||
## 51/50s
|
|
||||||
|
|
||||||
Crazy Homeless Individuals may be referred to as "51/50s" in the context of the game, indicating a person who may be experiencing a mental health crisis and could pose a potential threat to themselves or others.
|
|
||||||
|
|
||||||
- May loiter near entrances
|
|
||||||
- Can be unpredictable in behavior
|
|
||||||
- Often respond poorly to aggressive confrontation
|
|
||||||
- Require careful de-escalation and patience
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Special Systems
|
## Special Systems
|
||||||
|
|
||||||
## Reputation Meter
|
### Reputation
|
||||||
|
|
||||||
The player is evaluated on:
|
The shift evaluates peaceful resolutions, response time, radio accuracy, unnecessary confrontations, public complaints, property damage, entrance security, and whether assistance was justified. Calling armed help too often scores poorly even when no one is hurt.
|
||||||
|
|
||||||
- Number of incidents resolved peacefully
|
### Evidence and Body Camera
|
||||||
- Response time
|
|
||||||
- Accuracy of radio reports
|
|
||||||
- Number of unnecessary confrontations
|
|
||||||
- Public complaints
|
|
||||||
- Property damage
|
|
||||||
- Whether the entrance remained secure
|
|
||||||
|
|
||||||
A player who calls armed assistance too often should score poorly, even if no one was injured.
|
Interactions are recorded automatically. The body camera is a real state the player can toggle; a formal warning also arms it. The shift review uses the recorded events and evidence to show whether the response was justified.
|
||||||
|
|
||||||
## Evidence and Body Camera
|
### Crowd Reaction
|
||||||
|
|
||||||
Interactions are automatically recorded. After each shift, the player reviews incidents and sees whether their escalation was justified.
|
Nearby pedestrians react to the guard. Calm professionalism attracts little attention; aggression draws a crowd; visible weapons or responders can cause panic. Good de-escalation keeps the boulevard moving.
|
||||||
|
|
||||||
This encourages players to think before acting.
|
### Radio Limits
|
||||||
|
|
||||||
## Public Crowd Reaction
|
The emergency radio has a cooldown, and the call requires a short report. Assistance takes time to arrive, so the guard must keep monitoring the situation.
|
||||||
|
|
||||||
Nearby pedestrians react to the guard’s behavior:
|
|
||||||
|
|
||||||
- Calm professionalism attracts little attention
|
|
||||||
- Aggressive behavior creates a crowd
|
|
||||||
- Visible weapons or emergency responders cause panic
|
|
||||||
- Good de-escalation keeps the boulevard moving normally
|
|
||||||
|
|
||||||
## Limited Radio Battery or Cooldown
|
|
||||||
|
|
||||||
The emergency radio should not be infinitely available. A call has a cooldown and may require the player to give a short report first.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Shift Structure
|
## Shift Structure
|
||||||
|
|
||||||
Each shift lasts 10–15 minutes.
|
- **Early:** harmless tourists, delivery workers, people resting on the steps.
|
||||||
|
- **Midday:** heavier crowds, more entry attempts, more filming.
|
||||||
|
- **Evening:** lower light, more intoxicated pedestrians and aggressive groups.
|
||||||
|
- **Late:** a rare serious incident may test whether the player recognizes a real threat without overreacting to ordinary behavior.
|
||||||
|
|
||||||
### Early Shift
|
## Outcomes
|
||||||
|
|
||||||
Mostly harmless tourists, delivery workers, and people sitting on the stairs.
|
A successful shift keeps the building secure, resolves most incidents without force, keeps public access clear, makes justified emergency calls, avoids bystander injuries, and follows policy.
|
||||||
|
|
||||||
### Midday
|
A poor shift involves excessive force, confronting innocent people, ignoring a real threat, false reports, allowing unauthorized entry, causing a crowd or panic, or behaving recklessly even if the door stayed locked.
|
||||||
|
|
||||||
Crowds become heavier. More people attempt to enter the building or film interactions.
|
## Venues
|
||||||
|
|
||||||
### Evening
|
### Hollywood Boulevard Building
|
||||||
|
|
||||||
Lighting decreases. Intoxicated pedestrians and aggressive groups become more common.
|
The original scenario: a building entrance with steps, sidewalk, street lamps, and a restricted door. Tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in crisis, and rare violent threats approach from the boulevard.
|
||||||
|
|
||||||
### Late Shift
|
Authorized people are recognized or present a valid wristband. Wristbands vary by color, pattern, date, and event marking, and may be valid, expired, counterfeit, or missing.
|
||||||
|
|
||||||
A rare serious incident may occur, testing whether the player recognizes a genuine threat instead of overreacting to ordinary behavior.
|
### The Joke Shop
|
||||||
|
|
||||||
---
|
A comedy-club entrance. The door person checks IDs, tickets, reservations, guest-list status, and screens guests with a handheld wand under venue policy.
|
||||||
|
|
||||||
## Win and Loss Conditions
|
The wand does **not** identify a weapon automatically. Keys, belts, and phones can trigger false alarms; the player must ask the guest to identify the item and apply procedure. A guest with valid credentials can still require assistance if they threaten staff or patrons.
|
||||||
|
|
||||||
## Successful Shift
|
Typical events:
|
||||||
|
|
||||||
- Building remains secure
|
- a busy weekend show creates a long line;
|
||||||
- Most incidents are resolved without physical force
|
- a performer arrives through the guest entrance;
|
||||||
- Public access remains clear
|
- a guest has a reservation but no ID;
|
||||||
- Emergency calls are justified
|
- a fake or borrowed ID appears;
|
||||||
- No bystanders are injured
|
- a wand alert comes from keys or a belt;
|
||||||
- The player follows company policy
|
- a guest refuses screening but insists on entering;
|
||||||
|
- someone tries to follow an authorized person through the door;
|
||||||
|
- a late audience member arrives during the performance.
|
||||||
|
|
||||||
## Poor Performance
|
Joke Shop escalation follows the common ladder: direction, explanation, repeat request, refusal, manager/security assistance, emergency only for immediate danger.
|
||||||
|
|
||||||
- Excessive use of force
|
### Hollywood Boulevard Late Night TV Studio
|
||||||
- Repeatedly confronting innocent people
|
|
||||||
- Ignoring a real threat
|
|
||||||
- Making false radio reports
|
|
||||||
- Allowing repeated unauthorized entry
|
|
||||||
- Causing a crowd or panic
|
|
||||||
|
|
||||||
A shift can be technically “secure” but still receive a poor rating if the guard behaves recklessly.
|
A busy studio entrance with audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, influencers, and the same range of street behavior as the building scenario.
|
||||||
|
|
||||||
---
|
Staff and guests are recognized or present a wristband, credential, ticket, guest-list confirmation, or other approved marker. The player keeps the steps clear, protects arrivals and equipment, manages filming boundaries, watches for tailgating, and calls for help only when there is immediate danger.
|
||||||
|
|
||||||
## Venues and Scenarios
|
Typical events:
|
||||||
|
|
||||||
The game should support multiple venues, each with its own access rules, crowd patterns, equipment, and escalation procedures. The core gameplay remains the same: observe, verify access, communicate clearly, and use the lowest effective level of escalation.
|
- a crowd gathers for a celebrity arrival;
|
||||||
|
- tourists block the entrance for photographs;
|
||||||
|
- a performer sets up in the audience queue;
|
||||||
|
- an influencer ignores the filming boundary;
|
||||||
|
- a confused visitor believes the studio is open;
|
||||||
|
- an intoxicated pedestrian approaches the stage door;
|
||||||
|
- a trespasser blends into an audience group;
|
||||||
|
- an authorized guest arrives without a wristband;
|
||||||
|
- someone presents a counterfeit credential;
|
||||||
|
- a violent threat appears during a crowded arrival.
|
||||||
|
|
||||||
## The Joke Shop
|
## Presentation and Controls
|
||||||
|
|
||||||
**The Joke Shop** is a comedy club scenario where the player works as a door person at the entrance. The player is responsible for checking identification, confirming that guests are attending the correct show, and screening guests for weapons with a handheld security wand.
|
The game is first-person: WASD and mouse look, a focus cone on nearby people, a billboarded focus circle, `Tab`/mouse-wheel cycling for aim-free selection, and a radial action menu opened with left click or `Space`. `Shift` is precision walk. Gamepad input is supported, and keys/buttons can be remapped in Settings.
|
||||||
|
|
||||||
### Primary Responsibilities
|
The action wheel prioritizes de-escalation: Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio. Emergency stays on `R` and is not a wheel slice. `W` is walk, never the wand; `C` is the optional wand shortcut.
|
||||||
|
|
||||||
- Check each guest’s government-issued ID where required.
|
The visual direction is readable stylized realism:
|
||||||
- Confirm that the guest meets the venue’s age requirement.
|
|
||||||
- Verify that the ID appears valid and belongs to the person presenting it.
|
|
||||||
- Check tickets, reservations, or guest-list status.
|
|
||||||
- Use the handheld wand according to venue policy.
|
|
||||||
- Direct guests through the entrance without blocking the sidewalk.
|
|
||||||
- Refuse entry when a guest has an invalid ID, no valid admission, or a prohibited item.
|
|
||||||
|
|
||||||
### Guest Outcomes
|
- **Blender library:** 8 skinned NPC variants, 18 gear pieces, and 20 street/venue props, with baked albedo/normal/AO/ORM maps, a manifest, and procedural fallbacks. `Rig/Head` stays the focus anchor.
|
||||||
|
- **Realism pass:** AI-assisted per-phase skyline panoramas, v2 surface tiles with normal maps, wet asphalt at night, a warm day haze, phase-specific sun/exposure/fog, a visible flashlight beam, and denser foliage.
|
||||||
|
- **References and QA:** runtime UI art lives in `assets/images/ui/`; reference/art-direction sets live in `docs/screens/designs/` and `docs/screens/concepts/`; captures live in `docs/screenshots/`; realism runs live in `docs/screens/realism/`. `screen_designs.py compare/judge` and `tools/check_screenshots.sh` guard drift.
|
||||||
|
- **Storage convention:** see [ADR-0014](docs/adr/0014-runtime-image-root.md). Never overwrite an existing image; new outputs get a new path or `-vN` suffix.
|
||||||
|
|
||||||
A guest may have:
|
Remaining visual work is tracked in `TODO.md` (NPC fidelity and the two venue stages), not in this plan.
|
||||||
|
|
||||||
- A valid ID and ticket
|
Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [ADR-0008](docs/adr/0008-cone-focus-and-head-ring.md), [ADR-0009](docs/adr/0009-radial-action-menu.md), [ADR-0010](docs/adr/0010-procedural-npc-presentation.md), [ADR-0011](docs/adr/0011-aim-free-selection-and-precision-walk.md), [ADR-0012](docs/adr/0012-surface-materials-and-generated-props.md), [ADR-0013](docs/adr/0013-blender-authored-npc-characters.md).
|
||||||
- A valid ID but no ticket or reservation
|
|
||||||
- An expired, damaged, or questionable ID
|
|
||||||
- An ID that does not match the guest
|
|
||||||
- A prohibited item detected by the wand
|
|
||||||
- A harmless item that causes a false alarm
|
|
||||||
- No ID when identification is required
|
|
||||||
|
|
||||||
The player must make decisions based on the venue’s rules and observable evidence, not on a guest’s appearance or attitude. A calm guest with a valid ID should be admitted efficiently, while an aggressive guest with valid credentials may still require assistance if they threaten staff or other patrons.
|
|
||||||
|
|
||||||
### Joke Shop Escalation
|
|
||||||
|
|
||||||
1. **Nonverbal direction:** Raise a hand, point to the ID-check area, or gesture for the guest to wait.
|
|
||||||
2. **Routine explanation:** Explain the ID, ticket, or wand-check requirement.
|
|
||||||
3. **Repeat request:** Clearly state what is missing or what must happen before entry.
|
|
||||||
4. **Refusal of entry:** Keep the guest outside and direct them away from the entrance.
|
|
||||||
5. **Manager or security assistance:** Call for a supervisor or another staff member when a guest argues, refuses screening, or blocks the line.
|
|
||||||
6. **Emergency response:** Call emergency services only for an immediate threat, such as a weapon, assault, or serious attempt to force entry.
|
|
||||||
|
|
||||||
The handheld wand should not automatically identify an item as a weapon. The player must interpret the alert, ask the guest to identify the item, and follow venue procedure. The goal is to create a believable screening challenge while keeping the emphasis on communication and proportionate responses.
|
|
||||||
|
|
||||||
### Scenario Events
|
|
||||||
|
|
||||||
- A busy weekend comedy show creates a long line.
|
|
||||||
- A performer arrives through the guest entrance and must be recognized or verified.
|
|
||||||
- A guest forgets their ID but appears on the reservation list.
|
|
||||||
- A fake or borrowed ID is presented.
|
|
||||||
- A wand alert comes from keys, a belt buckle, or a phone.
|
|
||||||
- A guest refuses the screening but insists on entering.
|
|
||||||
- A disruptive guest attempts to follow another person through the door.
|
|
||||||
- A late-arriving audience member tries to enter during a performance.
|
|
||||||
|
|
||||||
## Hollywood Boulevard Late Night TV Studio
|
|
||||||
|
|
||||||
This scenario takes place outside a late-night television studio on Hollywood Boulevard. The guard manages the front steps, studio entrance, guest arrivals, deliveries, audience members, and the public crowd gathered around the building.
|
|
||||||
|
|
||||||
The studio encounters all of the NPC types described in the game plan:
|
|
||||||
|
|
||||||
- **Tourists** stopping to take photos or watch arrivals
|
|
||||||
- **Street performers** occupying the steps or performing near the entrance
|
|
||||||
- **Influencers and filmmakers** recording the guard, guests, or celebrity arrivals
|
|
||||||
- **Confused visitors** looking for the wrong entrance or trying to attend a show without understanding the access rules
|
|
||||||
- **Intoxicated pedestrians** approaching the entrance or disrupting the crowd
|
|
||||||
- **Trespassers** testing the doors, trying to follow authorized guests, or returning after being directed away
|
|
||||||
- **Violent threats**, which are rare but require careful recognition and emergency escalation
|
|
||||||
|
|
||||||
The studio also has authorized guests and staff. They must either be recognized by the guard or present a valid wristband, credential, guest-list confirmation, or other approved access marker. The player must check these credentials without holding up the audience line or allowing unauthorized people to enter.
|
|
||||||
|
|
||||||
### Studio Responsibilities
|
|
||||||
|
|
||||||
- Keep the front steps and entrance clear.
|
|
||||||
- Direct tourists and performers away from restricted areas.
|
|
||||||
- Check wristbands, credentials, tickets, or guest-list status.
|
|
||||||
- Admit authorized staff, performers, guests, and audience members.
|
|
||||||
- Manage influencers and filmmakers without unnecessarily obstructing lawful filming.
|
|
||||||
- Protect celebrity arrivals and production equipment from crowding.
|
|
||||||
- Coordinate with studio staff, production assistants, and additional security guards.
|
|
||||||
- Watch for attempts to follow authorized people through the entrance.
|
|
||||||
- Call for emergency assistance only when there is an immediate threat.
|
|
||||||
|
|
||||||
### Studio Scenario Events
|
|
||||||
|
|
||||||
- A crowd gathers to watch a celebrity arrive.
|
|
||||||
- Tourists stop on the steps and block the entrance for photographs.
|
|
||||||
- A street performer sets up in front of the audience queue.
|
|
||||||
- An influencer repeatedly ignores the restricted filming boundary.
|
|
||||||
- A confused visitor believes the studio is open to the public.
|
|
||||||
- An intoxicated pedestrian wanders toward the stage door.
|
|
||||||
- A trespasser tries to blend into an audience group.
|
|
||||||
- An authorized guest arrives without their wristband and must be verified another way.
|
|
||||||
- Someone presents a counterfeit wristband or credential.
|
|
||||||
- A violent threat appears during a crowded arrival, requiring the player to protect the entrance while calling for assistance.
|
|
||||||
|
|
||||||
The scenario should test the player’s ability to distinguish ordinary Hollywood Boulevard activity from a genuine security problem. The best solution is usually a simple gesture, clear direction, or credential check rather than immediate escalation.
|
|
||||||
|
|
||||||
Future venues can reuse this structure while changing the rules, equipment, staff roles, and types of access decisions.
|
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
## Engine: Godot 4
|
- **Engine:** Godot 4.3, GL Compatibility renderer (Web parity).
|
||||||
|
- **Language:** GDScript.
|
||||||
|
- **Architecture:** venue-driven simulation; shared systems plus `Resource`-based scenario data.
|
||||||
|
- **Navigation:** `NavigationRegion3D` / `NavigationAgent3D`.
|
||||||
|
- **NPC behavior:** lightweight custom finite state machines driven by `NpcProfile` resources.
|
||||||
|
- **UI/audio:** Godot `Control` nodes, themes, procedural audio, and JSON-backed localized strings.
|
||||||
|
- **Persistence:** JSON in `user://` for settings and shift history.
|
||||||
|
- **Testing:** custom headless runner (`src/test/test_runner.gd`); GUT was evaluated and not adopted. Python tooling tests cover the asset/screenshot pipelines.
|
||||||
|
- **Source control/CI:** git on Gitea; `.gitea/workflows/build.yml` runs headless checks and builds/publishes releases on tags.
|
||||||
|
- **Targets:** Windows, Linux, macOS, and Web. Release artifacts include portables, an NSIS installer, an AppImage, and a Web zip.
|
||||||
|
|
||||||
The game is built with the Godot Engine 4.x editor. Godot was chosen for its free and open-source license, built-in 3D rendering, navigation, animation, audio, and UI systems, and its low weight for fast prototyping and iteration.
|
## Versioning and Delivery
|
||||||
|
|
||||||
## Language: GDScript
|
`config/version` in `project.godot` is the single version source (currently `0.7.0`). Releases are tagged `v<version>`; the CI pipeline builds and publishes the six artifacts. Version bumps follow [Semantic Versioning](https://semver.org/). See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands.
|
||||||
|
|
||||||
Gameplay logic is written in GDScript. It is tightly integrated with the engine, fast to iterate on, and avoids an external build toolchain for the prototype phase.
|
|
||||||
|
|
||||||
## Architecture Mapping
|
|
||||||
|
|
||||||
The venue-driven architecture maps onto Godot as follows:
|
|
||||||
|
|
||||||
- **Nodes and scenes:** Each major system (`ShiftCoordinator`, `NPCController`, `InteractionSystem`, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`, `ShiftReview`) is a Godot node or scene.
|
|
||||||
- **Scenarios as resources:** `VenueScenario` and content such as `NpcProfile`, `Dialogue`, `EventPool`, and `AccessPolicy` are defined as custom Godot `Resource` types so venue content is data-driven and inspectable in the editor.
|
|
||||||
- **Navigation:** `NavigationRegion3D` and `NavigationAgent3D` drive NPC and player movement.
|
|
||||||
- **NPC behavior:** Lightweight custom finite state machines built on the `NPCController` node, with behavior traits coming from `NpcProfile` resources.
|
|
||||||
- **UI:** Godot `Control` nodes and themes for prompts, access inspection, radio reports, dialogue, timers, and the shift review screen.
|
|
||||||
- **Audio:** Godot audio buses and `AudioStreamPlayer` nodes for voices, crowd noise, radio, and environment sound.
|
|
||||||
|
|
||||||
## Content and Data
|
|
||||||
|
|
||||||
- Scenario and entity configuration uses typed Godot `Resource` files (`.tres`), created either in the editor or via scripts.
|
|
||||||
- Localized dialogue and text are stored in JSON files when they need to be editable outside the editor.
|
|
||||||
- Save data and shift results use JSON in Godot's `user://` directory.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
- Unit and scenario tests use a lightweight custom headless runner
|
|
||||||
(`src/test/test_runner.gd`). GUT was evaluated but requires the editor plugin.
|
|
||||||
- Tests run in headless mode so rules, access verification, escalation, and scoring can be verified without rendering or user input.
|
|
||||||
|
|
||||||
## Version Control and CI
|
|
||||||
|
|
||||||
- Source is tracked with git.
|
|
||||||
- Gitea Actions (`.gitea/workflows/build.yml`) runs headless Godot tests on push/PR and exports
|
|
||||||
Linux/Windows/macOS desktop builds plus the NSIS installer and AppImage on tags.
|
|
||||||
|
|
||||||
## Planned Commands
|
|
||||||
|
|
||||||
The following commands apply once the Godot project is scaffolded:
|
|
||||||
|
|
||||||
- Open the editor: `godot --editor --path .`
|
|
||||||
- Run the game: `godot --path .`
|
|
||||||
- Run tests headlessly: `godot --headless --path . res://src/test/test_runner.tscn`
|
|
||||||
|
|
||||||
## Export Targets
|
|
||||||
|
|
||||||
Desktop is the first target: Windows, Linux, and macOS. Web export is a later option.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### What is wrong today
|
|
||||||
|
|
||||||
- `W` is walk-forward in the input map and wand-screening in `GameManager._handle_key`. Walking at someone with a wand venue fires the wand.
|
|
||||||
- `A` / `D` are strafe and also allow/deny during inspection.
|
|
||||||
- `Q` is polite direction and is also bound as `cancel`.
|
|
||||||
- Focus is an 8 m physics ray from the camera. Missing a capsule by a few pixels clears the target. There is no mark in the world that you have someone.
|
|
||||||
- NPC bodies are colored capsules with props. They read as markers, not people.
|
|
||||||
- Actions live on number keys `1`–`6` plus `Q` / `E` / `W` / `R`. That is a cheat-sheet, not a verb you pick while looking at a person.
|
|
||||||
- `Esc` only toggles mouse capture. There is no "I changed my mind" for an action in progress.
|
|
||||||
|
|
||||||
### Movement and look
|
|
||||||
|
|
||||||
Keep first-person WASD + mouse look. Fix the feel and stop stealing those keys.
|
|
||||||
|
|
||||||
- Body yaw from mouse X, head pitch from mouse Y, pitch clamped (already `-80` to `80`).
|
|
||||||
- Move relative to body yaw only (already). Add short acceleration / deceleration so starts and stops are not instant.
|
|
||||||
- Gravity only when `not is_on_floor()`; `floor_snap_length` so the steps work.
|
|
||||||
- Mouse look runs only while `MOUSE_MODE_CAPTURED` and no modal UI is open (radial, inspection, wand, radio, shift review).
|
|
||||||
- Walk speed stays around 3.2 m/s. No sprint, no crouch in this pass.
|
|
||||||
- While the radial is open: freeze look and WASD so `A` / `D` can cycle the menu. Recapture the mouse when the menu closes.
|
|
||||||
- `Esc` with a modal open closes that modal and does nothing else. `Esc` with nothing open keeps the current capture/release toggle.
|
|
||||||
|
|
||||||
Equipment stays on its own keys because it is not about a person: `G` flashlight, `L` door lock, `Tab` cycle nearby people, `V` venue (debug), `F` debug overlay. `R` stays the dedicated emergency radio (hold-to-confirm on the existing panel). Those do not go on the wheel.
|
|
||||||
|
|
||||||
Wand leaves `W`. It is a slice on the wheel (greyed when the venue has no wand) and an optional extra key that is not WASD, suggested `C`.
|
|
||||||
|
|
||||||
### Focus area
|
|
||||||
|
|
||||||
Replace the hairline ray as the primary picker.
|
|
||||||
|
|
||||||
- A **focus cone** in front of the camera: people within about 10 m whose head projects inside a screen-space circle around the crosshair (about 90 px / 10 degrees).
|
|
||||||
- If several heads sit in the circle, pick the one closest to the center, then the nearer one if tied.
|
|
||||||
- The person must be in front (dot with camera forward > 0) and not fully occluded by the building. A short ray to the head is enough for occlusion.
|
|
||||||
- `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.
|
|
||||||
|
|
||||||
### Head ring
|
|
||||||
|
|
||||||
When a person is in focus, draw a thin ring around their head.
|
|
||||||
|
|
||||||
- Attach to the avatar `Head` node, sitting just above the skull, billboarded to the camera so it stays a circle on screen.
|
|
||||||
- Slow spin is fine; no ping-pong scale spam.
|
|
||||||
- Idle focus: pale gold, 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.
|
|
||||||
- One ring 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.
|
|
||||||
|
|
||||||
### Round action HUD
|
|
||||||
|
|
||||||
A doughnut / pie menu centered on the screen, opened on the focused person.
|
|
||||||
|
|
||||||
**Open**
|
|
||||||
|
|
||||||
- Left mouse button, or `Space`, while someone is in focus and no other modal is open.
|
|
||||||
- If nobody is in focus, log the existing "aim first" line and do not open.
|
|
||||||
- The click that opens the menu must not also pick a slice.
|
|
||||||
|
|
||||||
**While open**
|
|
||||||
|
|
||||||
- Mouse becomes visible. Look and walk freeze.
|
|
||||||
- Center of the doughnut shows the focused person's `display_name`.
|
|
||||||
- Slices, clockwise from the top, de-escalation first:
|
|
||||||
1. Raise hand (stop / wait)
|
|
||||||
2. Point away
|
|
||||||
3. Head shake
|
|
||||||
4. Polite direction (old `Q`)
|
|
||||||
5. Inspect access (old `E`)
|
|
||||||
6. Wand (grey if the venue has no wand)
|
|
||||||
7. Formal warning
|
|
||||||
8. Block
|
|
||||||
9. Radio assist
|
|
||||||
- Emergency is **not** on the wheel (`R` only).
|
|
||||||
- Highlight follows the mouse angle when the cursor is over the ring. `Left` / `Right` or `A` / `D` step the highlight. Wrap around.
|
|
||||||
|
|
||||||
**Confirm**
|
|
||||||
|
|
||||||
- Click a slice: that action runs, menu closes, mouse recaptured.
|
|
||||||
- `Space` while a slice is highlighted: same.
|
|
||||||
- Inspect or wand: close the wheel, then open the existing inspect / wand panel. Allow / deny on that panel stay `A` / `D` (walk is frozen only while the wheel is up; once the inspect panel is open, WASD can move again, so allow/deny on `A` / `D` remains a press handler and must ignore repeats / echo, which it already does). Safer: keep walk frozen for inspect/wand too, until the panel closes. Prefer freeze until the whole interaction ends.
|
|
||||||
- Radio assist: close the wheel, open the existing radio panel.
|
|
||||||
|
|
||||||
**Cancel**
|
|
||||||
|
|
||||||
- `Esc` or right mouse button: hide the menu, no action, no recorder event, recapture mouse.
|
|
||||||
- Focus and the head ring 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.
|
|
||||||
|
|
||||||
### NPC display
|
|
||||||
|
|
||||||
Stay on the procedural capsule rig (no Mixamo / GLB dependency this pass). Make them read as people at conversation distance.
|
|
||||||
|
|
||||||
- Neck between torso and head; shoes; simple hair cap (color from seed); two dark eye spheres on the face so a head has a front.
|
|
||||||
- Keep archetype props (camera, box, hat, sign, bag). Keep height / brightness variation from the visual seed.
|
|
||||||
- Collision capsule matches the scaled height so the focus ray / cone hits the body you see.
|
|
||||||
- `Head` stays a named node other systems can find (`get_node("Rig/Head")`) for the ring and the cone.
|
|
||||||
- Walk bob and the "look at the guard when noticed" cue stay.
|
|
||||||
- Do not put the internal type name (`Trespasser`, `Violent Threat`) on the body. The player should judge from clothes, props, and behavior. The debug overlay (`F`) can still print the type.
|
|
||||||
|
|
||||||
AI textures in `assets/textures/ai/` stay out of this pass unless a specific map (shirt, hair) is already the right size.
|
|
||||||
|
|
||||||
### Input map (target)
|
|
||||||
|
|
||||||
| Action | Bind |
|
|
||||||
| --- | --- |
|
|
||||||
| Walk | WASD / arrows (arrows **do not** walk while the wheel is open; they step the highlight) |
|
|
||||||
| Look | Mouse, captured |
|
|
||||||
| Open / confirm action menu | LMB, `Space` |
|
|
||||||
| Cancel menu | RMB, `Esc` |
|
|
||||||
| Step highlight | `A` / `D`, left / right arrows |
|
|
||||||
| Emergency radio | `R` |
|
|
||||||
| Flashlight | `G` |
|
|
||||||
| Door lock | `L` |
|
|
||||||
| Cycle nearby people | `Tab` |
|
|
||||||
| Wand shortcut | `C` (not `W`) |
|
|
||||||
| Inspect shortcut | `E` still allowed when the wheel is closed |
|
|
||||||
|
|
||||||
### Files this pass is expected to touch
|
|
||||||
|
|
||||||
- `src/game/guard_controller.gd` — cone focus, no look while modal, accel, floor snap.
|
|
||||||
- `src/game/npc_avatar.gd` — face / hair / neck, head marker, ring toggle.
|
|
||||||
- `src/game/ui/action_radial.gd` — new Control, `_draw` doughnut, slice pick.
|
|
||||||
- `src/game/ui/hud.gd` — host the radial; stop teaching number-key verbs as the only path.
|
|
||||||
- `src/game/game_manager.gd` — input routing, freeze flags, open/confirm/cancel.
|
|
||||||
- `project.godot` — input map.
|
|
||||||
- `src/core/strings.gd` — controls line, aim-first, no new tone-of-voice.
|
|
||||||
- `src/test/tests/test_focus_and_radial.gd` — cone pick, wrap, cancel emits nothing, `W` is not wand.
|
|
||||||
- README controls table and smoke checklist.
|
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||
A useful guiding rule:
|
|
||||||
|
|
||||||
> **The player wins by choosing the lowest level of escalation that keeps everyone safe.**
|
> **The player wins by choosing the lowest level of escalation that keeps everyone safe.**
|
||||||
|
|
||||||
|
## Future Ideas
|
||||||
|
|
||||||
|
- A rare Bob Dobbs cameo (Easter-egg appearance; placement note in `MEMORY.md`).
|
||||||
|
- Specific unusual NPC behaviors, such as slapping their own face, using hands as scissors, or interacting with props unexpectedly.
|
||||||
|
- Live 3D body-camera replay; additional venues or seasonal events; real voice/dialogue audio; an image-to-mesh prop pipeline.
|
||||||
@@ -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,271 +13,90 @@ 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, the Joke Shop alley, the TV Studio arrival, 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.7.0** — distinct Joke Shop and TV Studio geometry, fitted NPC gear,
|
||||||
|
> and the post-v0.6.0 realism/visual-alignment work (v2 skylines/textures, UI mockup
|
||||||
|
> art, flashlight beam, HUD fixes).
|
||||||
> Releases are built automatically when a `vX.Y.Z` tag is pushed
|
> 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
|
||||||
> (NSIS + appimagetool) and uploads with the portable binaries.
|
> (NSIS + appimagetool) and uploads with the portable binaries.
|
||||||
>
|
>
|
||||||
> Note: on this host, downloading release files currently requires sign-in;
|
|
||||||
> the page and tag URLs are public.
|
|
||||||
>
|
|
||||||
> macOS builds are unsigned: right-click → Open, or clear the quarantine with
|
> 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
|
||||||
[Run and Test](#run-and-test) below.
|
[Setup and Run](#setup-and-run) below.
|
||||||
|
|
||||||
The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4.3 + GDScript): a procedural Hollywood Boulevard entrance, a first-person guard, simulated NPCs with intents and reactions, the six-level escalation model, access inspection and admission decisions, radio calls with credibility checks, reputation, crowd mood, and an end-of-shift review.
|
The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4.3 + GDScript): a procedural Hollywood Boulevard entrance, a first-person guard, simulated NPCs with intents and reactions, the six-level escalation model, access inspection and admission decisions, radio calls with credibility checks, reputation, crowd mood, and an end-of-shift review.
|
||||||
|
|
||||||
## Game Overview
|
## Game Overview
|
||||||
|
|
||||||
A typical shift lasts 10–15 minutes. The player:
|
A typical shift lasts 10–15 minutes: watch the entrance and sidewalk, judge why
|
||||||
|
each person is approaching, verify access with recognition, wristbands, IDs,
|
||||||
1. Watches the entrance, steps, queue, and surrounding sidewalk.
|
tickets or credentials, and escalate only when necessary. The game rewards calm
|
||||||
2. Determines why each person is approaching.
|
resolution over force. The full mechanics — the three venues, the six-level
|
||||||
3. Uses simple gestures or polite directions first.
|
escalation model, and the core systems — are in
|
||||||
4. Verifies authorized access using recognition, wristbands, credentials, tickets, or guest lists.
|
[`docs/gameplay.md`](docs/gameplay.md); the design rationale lives in
|
||||||
5. Escalates only when a person refuses directions, attempts unauthorized entry, or presents a genuine threat.
|
[`PLAN.md`](PLAN.md).
|
||||||
6. Records the incident through body-camera and radio systems.
|
|
||||||
7. Finishes the shift with a performance review based on safety, judgment, and professionalism.
|
|
||||||
|
|
||||||
The game rewards calm resolutions rather than aggressive behavior. Emergency assistance and armed responders are rare last-resort systems with meaningful consequences.
|
|
||||||
|
|
||||||
## Planned Venues
|
|
||||||
|
|
||||||
### Hollywood Boulevard Building
|
|
||||||
|
|
||||||
The original scenario places the guard at the top of the steps outside a fictional building. Tourists, street performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people experiencing a mental-health crisis, and rare violent threats may approach the entrance.
|
|
||||||
|
|
||||||
Authorized people must either be recognized by the guard or present a valid wristband. Wristbands can have different colors, patterns, dates, or event markings. They may be valid, expired, counterfeit, or missing.
|
|
||||||
|
|
||||||
### The Joke Shop
|
|
||||||
|
|
||||||
The Joke Shop is a comedy-club scenario. The door person checks IDs, tickets, reservations, and guest-list status, then uses a handheld security wand according to venue policy. The player must interpret wand alerts rather than treating every alert as proof of a weapon; keys, belts, phones, and other harmless objects can trigger false alarms.
|
|
||||||
|
|
||||||
### Hollywood Boulevard Late Night TV Studio
|
|
||||||
|
|
||||||
This scenario combines the previously defined NPC types with a busy television-studio entrance. The player manages audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, street performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people experiencing a mental-health crisis, and rare violent threats.
|
|
||||||
|
|
||||||
The studio uses wristbands, credentials, tickets, guest-list confirmation, and personal recognition to authorize entry. Crowd control, filming boundaries, following authorized guests through the door, and counterfeit credentials are key events.
|
|
||||||
|
|
||||||
## Escalation Model
|
|
||||||
|
|
||||||
The common escalation model has six levels:
|
|
||||||
|
|
||||||
0. **Observation** — Decide whether intervention is necessary.
|
|
||||||
1. **Simple deterrence and polite direction** — Raise an open hand, make eye contact, point toward the sidewalk or check-in area, shake the head, or give a short instruction.
|
|
||||||
2. **Formal warning** — Clearly explain the rule and request compliance.
|
|
||||||
3. **Blocking and physical positioning** — Secure the entrance, maintain distance, preserve an escape route, and deny access without starting a fight.
|
|
||||||
4. **Radio for security assistance** — Request a supervisor, second guard, building management, or other appropriate support.
|
|
||||||
5. **Emergency response** — Hold the emergency-radio button and confirm only when there is an immediate threat, such as a weapon, assault, forced entry, or serious threat to occupants.
|
|
||||||
|
|
||||||
An off-duty law-enforcement officer or armed emergency responder may arrive during a valid emergency. This character is not intended to be a routine stronger attack. Their role is crisis control, evacuation, restraint, arrest, or other appropriate intervention, with force governed by the situation and meaningful consequences for unjustified calls.
|
|
||||||
|
|
||||||
## Core Systems
|
|
||||||
|
|
||||||
- **NPC behavior:** NPCs have different motivations, temperaments, compliance levels, and reactions to the player’s approach.
|
|
||||||
- **Access verification:** Recognition, wristbands, IDs, tickets, credentials, and guest lists determine who may enter.
|
|
||||||
- **Nonverbal deterrence:** Small actions such as raising a hand can resolve low-risk situations without dialogue or force.
|
|
||||||
- **Dialogue:** Polite directions and warnings affect outcomes; simple gestures can resolve low-risk situations without dialogue.
|
|
||||||
- **Escalation:** The player chooses the lowest effective response.
|
|
||||||
- **Radio assistance:** Calls require useful information and may take time to arrive.
|
|
||||||
- **Evidence:** Body-camera records support shift review and accountability.
|
|
||||||
- **Crowd reaction:** Aggressive behavior, visible weapons, and emergency response can create panic or attract a crowd.
|
|
||||||
- **Reputation:** Peaceful resolutions, accurate reports, low property damage, and justified calls improve performance; unnecessary confrontations and false reports reduce it.
|
|
||||||
- **Shift review:** The player receives feedback on access decisions, escalation choices, public complaints, injuries, and security outcomes.
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
The planned solution uses a venue-driven simulation architecture. Venue configuration supplies rules, access types, NPC pools, equipment, events, and escalation policies. Shared simulation systems provide movement, perception, interactions, dialogue, access verification, escalation, radio calls, crowds, reputation, evidence, and shift review.
|
|
||||||
|
|
||||||
```text
|
|
||||||
Venue Scenario / Rules
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Shift Coordinator -----> Event Scheduler
|
|
||||||
| |
|
|
||||||
v v
|
|
||||||
NPC Simulation <---------- Interaction System
|
|
||||||
| |
|
|
||||||
v v
|
|
||||||
Access Verification ----> Escalation / Response
|
|
||||||
| |
|
|
||||||
+-------------> Reputation / Evidence
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Shift Review
|
|
||||||
```
|
|
||||||
|
|
||||||
The architecture maps onto Godot 4 nodes, scenes, and custom resources as described in [Tech Stack](#tech-stack). See the full arc42-style outline in [`docs/architecture.md`](docs/architecture.md) and the decisions in [`docs/adr/`](docs/adr/).
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
- **Engine:** Godot Engine 4.x
|
|
||||||
- **Language:** GDScript
|
|
||||||
- **Architecture:** Venue-driven simulation using nodes, scenes, and custom `Resource`-based scenario data
|
|
||||||
- **Navigation:** `NavigationRegion3D` and `NavigationAgent3D`
|
|
||||||
- **NPC behavior:** Custom lightweight finite state machines driven by `NpcProfile` resources
|
|
||||||
- **UI and audio:** Godot `Control` nodes, themes, audio buses, and `AudioStreamPlayer` nodes
|
|
||||||
- **Art assets:** authored PNGs and the in-game generated set in `assets/textures/ai/game_ready/`; 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.
|
|
||||||
- **Testing:** GUT (Godot Unit Test) in headless mode
|
|
||||||
- **Source control:** git on a Gitea host with Gitea Actions (`.gitea/workflows/build.yml`) for headless tests and desktop release builds
|
|
||||||
- **Targets:** Windows, Linux, and macOS desktop first; web later
|
|
||||||
|
|
||||||
### Image Asset Tools
|
|
||||||
|
|
||||||
Install the Pillow dependency used by the existing texture generator and the asset tools:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -m pip install -r requirements-tools.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Generate a role-sized image (generation requires `OPENROUTER_API_KEY`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 tools/asset_pipeline.py generate --prompt "weathered red brick" --role tile --output assets/textures/brick_extra.png
|
|
||||||
```
|
|
||||||
|
|
||||||
Generated images are resized to the selected role profile. Non-square requests use
|
|
||||||
the validated `openai/gpt-5-image-mini` source ratio and crop to the final role dimensions.
|
|
||||||
Transparent glyphs can be recolored while preserving their alpha mask with
|
|
||||||
`--foreground '#ffffff'`.
|
|
||||||
|
|
||||||
Crop uses the image center by default; `--x` and `--y` select an explicit crop origin. Resize defaults to aspect-preserving `contain`; use `--fit cover` to crop or `--fit stretch` to distort to the exact dimensions.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 tools/asset_pipeline.py crop assets/textures/source.png assets/textures/crop.png --width 256 --height 128
|
|
||||||
python3 tools/asset_pipeline.py resize assets/textures/source.png assets/images/backgrounds/scene.png --width 1920 --height 1080 --fit cover
|
|
||||||
python3 tools/asset_pipeline.py seamless assets/textures/rough_tile.png assets/textures/tile.png
|
|
||||||
```
|
|
||||||
|
|
||||||
Audit accepts image files or directories. `--role auto` infers common roles from paths and filenames; explicit roles include `tile`, `texture`, `icon`, `card`, `banner`, `backdrop`, `background`, `overlay`, `app_icon`, and `screenshot`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 tools/asset_pipeline.py audit assets/textures
|
|
||||||
python3 tools/asset_pipeline.py audit assets/images/backgrounds --role background --json
|
|
||||||
python3 -m unittest discover -s tools -p 'test_*.py'
|
|
||||||
```
|
|
||||||
|
|
||||||
The audit classifies measurable fit issues as `good` or `bad` based on role-specific dimensions, aspect ratio, alpha requirements, and tile-edge continuity. It lists perspective/projection as a visual review item because image semantics cannot be judged reliably by these deterministic checks.
|
|
||||||
|
|
||||||
### Screen design references
|
|
||||||
|
|
||||||
Screen mockups and screenshot comparison use `tools/screen_designs.py`. It turns the
|
|
||||||
screen descriptions in `docs/` (README captions, ADRs 0007–0010, `PLAN.md`, and
|
|
||||||
`architecture.md`) into reference images under `docs/screens/designs/`, then uses
|
|
||||||
those images as the expected state to check the game's captures.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Deterministic wireframes (offline, no API key)
|
|
||||||
python3 tools/screen_designs.py generate --draft
|
|
||||||
|
|
||||||
# Photorealistic mockups via OpenRouter (requires OPENROUTER_API_KEY)
|
|
||||||
python3 tools/screen_designs.py generate
|
|
||||||
|
|
||||||
# Compare docs/screenshots/*.png against the designs
|
|
||||||
python3 tools/screen_designs.py compare
|
|
||||||
python3 tools/screen_designs.py compare --json
|
|
||||||
```
|
|
||||||
|
|
||||||
`compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank
|
|
||||||
frame, and large layout/palette/detail distance. It is a regression gate, not a
|
|
||||||
semantic judge — pixel and perceptual metrics cannot decide whether a frame's
|
|
||||||
composition is *right*, so a vision-model pass is the documented follow-up (see
|
|
||||||
`TODO.md`). Regenerate the designs after changing screen layout, then re-run
|
|
||||||
`compare` after regenerating the game screenshots with
|
|
||||||
`src/test/capture_screenshots.gd`.
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
1. Download the Godot 4.3 editor (official build) and put `godot` on your PATH.
|
|
||||||
2. Open or run the project from this directory.
|
|
||||||
|
|
||||||
### Run and Test
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Open in the editor
|
|
||||||
godot --editor --path .
|
|
||||||
|
|
||||||
# Run the game
|
|
||||||
godot --path .
|
|
||||||
|
|
||||||
# Headless smoke boot (120 frames then exit)
|
|
||||||
godot --headless --path . --quit-after 120
|
|
||||||
|
|
||||||
# Run all unit tests headlessly (exit code 0 = pass)
|
|
||||||
godot --headless --path . --script res://src/test/test_runner.gd
|
|
||||||
|
|
||||||
# Save a screenshot to user:// (windowed)
|
|
||||||
godot --path . --script res://src/test/take_screenshot.gd
|
|
||||||
|
|
||||||
# Windowed control smoke: focus ring + action wheel, saves PNGs, exits 1 on failure
|
|
||||||
godot --path . --script res://src/test/smoke_focus_radial.gd
|
|
||||||
|
|
||||||
# Session-shell smoke: menu → shift → pause → settings → menu (headless or windowed)
|
|
||||||
godot --headless --path . --script res://src/test/smoke_session_shell.gd
|
|
||||||
|
|
||||||
# PLAN-verb smoke: recognize, request reason, body camera, warning, blocking
|
|
||||||
godot --headless --path . --script res://src/test/smoke_plan_verbs.gd
|
|
||||||
|
|
||||||
# Crowd smoke: rubbernecking on aggression, panic clearing the entrance
|
|
||||||
godot --headless --path . --script res://src/test/smoke_crowd.gd
|
|
||||||
|
|
||||||
# Emergency smoke: unjustified call, investigation note, evacuation aftermath
|
|
||||||
godot --headless --path . --script res://src/test/smoke_emergency.gd
|
|
||||||
```
|
|
||||||
|
|
||||||
The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor.
|
|
||||||
|
|
||||||
### Controls
|
|
||||||
|
|
||||||
| Input | Action |
|
|
||||||
| --- | --- |
|
|
||||||
| W/A/S/D + mouse | Move and look |
|
|
||||||
| Space / left click | Action wheel for the focused person (Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio) |
|
|
||||||
| A / D or ←/→ | Step through the action wheel (while it is open) |
|
|
||||||
| Space / left click | Confirm the highlighted action (while the wheel is open) |
|
|
||||||
| Esc / right click | Close the top panel; Esc with nothing open pauses the shift (Esc again resumes) |
|
|
||||||
| ← / → or ↑ / ↓ | Navigate the venue menu and the pause / settings panels |
|
|
||||||
| Enter | Start the highlighted venue from the menu, or confirm in a panel |
|
|
||||||
| 1 / 2 / 3 | Quick gestures without the wheel: raise hand, point, head shake (Level 1) |
|
|
||||||
| Q | Polite direction (Level 1) |
|
|
||||||
| 4 | Formal warning (Level 2) |
|
|
||||||
| 5 | Blocking and positioning (Level 3) |
|
|
||||||
| 6 | Security assistance radio (Level 4) |
|
|
||||||
| R | Emergency radio, hold on-screen button ~1.6s (Level 5) |
|
|
||||||
| E | Inspect access artifacts / close inspection |
|
|
||||||
| C | Handheld wand screening (wand venues only) |
|
|
||||||
| A / D | Allow / deny entry during inspection or wand check |
|
|
||||||
| V | Cycle venue (debug shortcut; restart via V restarts the current shift) |
|
|
||||||
| Tab | Cycle target among nearby NPCs (aim-free alternative) |
|
|
||||||
| G | Toggle flashlight (widens NPC perception at night) |
|
|
||||||
| B | Toggle the body camera; a formal warning switches it on automatically |
|
|
||||||
| L | Lock / unlock the restricted door (thwarts forced entry) |
|
|
||||||
| F | Toggle debug overlay |
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
The full arc42-style architecture documentation lives in [`docs/architecture.md`](docs/architecture.md). It covers goals, constraints, context, solution strategy, building blocks, runtime views, deployment, cross-cutting concepts, quality requirements, risks, a glossary, the CI/CD build pipeline, semantic versioning, and release management.
|
A venue-driven simulation: shared systems (movement, perception, interaction,
|
||||||
|
access verification, escalation, radio, crowd, reputation, evidence, shift
|
||||||
|
review) driven by per-venue scenario data, on Godot 4 + GDScript. The full
|
||||||
|
arc42-style document is [`docs/architecture.md`](docs/architecture.md);
|
||||||
|
decisions are recorded as ADRs in [`docs/adr/`](docs/adr/).
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Engine:** Godot 4.x · **Language:** GDScript
|
||||||
|
- **NPCs:** Blender-authored skinned library (`assets/models/blender/`) with procedural fallbacks; lightweight FSMs on `NpcProfile`
|
||||||
|
- **World:** procedural `WorldBuilder` + generated prop models
|
||||||
|
- **Art:** v2 skyline/surface textures in `assets/textures/ai/game_ready/v2/picks/`; UI/scene art in `assets/images/`
|
||||||
|
- **Asset tooling:** `asset_pipeline.py`, `model_pipeline.py`, `npc_pipeline.py`, `blender_assets.py`, `scene_concepts.py`, `screen_designs.py`, `check_screenshots.sh`
|
||||||
|
- **Testing:** custom headless runner (`src/test/test_runner.gd`)
|
||||||
|
- **Targets:** Windows, Linux, macOS, Web
|
||||||
|
|
||||||
|
The asset pipelines, model tools, and screenshot gates are documented in
|
||||||
|
[`docs/asset-tooling.md`](docs/asset-tooling.md).
|
||||||
|
|
||||||
|
## Setup and Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m pip install -r requirements-tools.txt
|
||||||
|
|
||||||
|
godot --editor --path . # open in the editor
|
||||||
|
godot --path . # run the game
|
||||||
|
godot --headless --path . --script res://src/test/test_runner.gd # run all tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Full setup, every run/test/smoke command, and the controls table are in
|
||||||
|
[`docs/getting-started.md`](docs/getting-started.md).
|
||||||
|
|
||||||
## Project Documentation
|
## Project Documentation
|
||||||
|
|
||||||
@@ -286,66 +105,25 @@ The full arc42-style architecture documentation lives in [`docs/architecture.md`
|
|||||||
- [`TODO.md`](TODO.md) — Active checkbox task list.
|
- [`TODO.md`](TODO.md) — Active checkbox task list.
|
||||||
- [`DONE.md`](DONE.md) — Completed task sections.
|
- [`DONE.md`](DONE.md) — Completed task sections.
|
||||||
- [`MEMORY.md`](MEMORY.md) — Persistent session notes.
|
- [`MEMORY.md`](MEMORY.md) — Persistent session notes.
|
||||||
|
- [`CHANGELOG.md`](CHANGELOG.md) — User-facing changes by version.
|
||||||
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
|
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
|
||||||
|
- [`docs/gameplay.md`](docs/gameplay.md) — Gameplay systems, venues, and the escalation model.
|
||||||
|
- [`docs/asset-tooling.md`](docs/asset-tooling.md) — Asset pipelines, model tools, and screenshot gates.
|
||||||
|
- [`docs/getting-started.md`](docs/getting-started.md) — Setup, run/test commands, and controls.
|
||||||
|
- [`docs/development-status.md`](docs/development-status.md) — Implemented features and what's next.
|
||||||
|
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md) — Manual smoke-test checklist.
|
||||||
|
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
|
||||||
- [`docs/adr/`](docs/adr/) — Architecture Decision Records.
|
- [`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.
|
||||||
|
|
||||||
## Development Status
|
## Development Status
|
||||||
|
|
||||||
Implemented so far:
|
v0.7.0 shipped distinct venue geometry: the Joke Shop alley and TV Studio
|
||||||
|
entrance no longer share the Hollywood layout. It builds on v0.6.1's fitted NPC
|
||||||
- Godot 4.3 project scaffold, input map, procedural world builder
|
gear and v0.6.0's Blender asset library, baked AO and environment lift. See
|
||||||
- Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`
|
[`docs/development-status.md`](docs/development-status.md) for the full
|
||||||
- Three authored scenarios (Hollywood Boulevard building, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools
|
implemented list and [`TODO.md`](TODO.md) for what's next. Manual verification
|
||||||
- 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)
|
uses the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
|
||||||
- Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event
|
|
||||||
- First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review
|
|
||||||
- Control pass: focus cone on NPC heads with a billboarded 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`)
|
|
||||||
- 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
|
|
||||||
- Routine traffic despawns after a while at its waypoint so the crowd cap no longer fills with idle standers
|
|
||||||
- PLAN verbs: eye contact (distinct Level 1 cue), recognizing a person as a player decision recorded as access evidence, a body-camera flag that a formal warning arms (and `B` toggles), blocking that physically stations the guard between the person and the door, and a Level 3 request-reason ask answered from the NPC's `dialogue_lines`
|
|
||||||
- Incidents are now actually opened on focus and closed at shift end, so player actions, access evidence, escalation levels, and unresolved interactions reach the review (previously every recorded event was dropped)
|
|
||||||
- Crowd presentation: aggressive behavior and responder arrivals make idle bystanders stop and face the incident; panic sends people away from the door (and they leave the scene). A busy 16-NPC entrance measured ~1.2 ms/frame, so no LOD/culling was needed at the current cap
|
|
||||||
- Distant skyline ring: four alpha-scissored planes at 200 m read as a hazy city on the horizon (the baked backdrop used to show as a black slab), with a larger ground plane so the buildings meet the street
|
|
||||||
- Flashlight aims straight ahead and lights the person/door being checked instead of the ground in front of the guard
|
|
||||||
- Emergency aftermath: the `evacuation` outcome locks the door and clears the entrance, an emergency call that the situation did not justify opens an investigation and applies the false-report hit, and the shift review names the responder outcome and any investigations
|
|
||||||
- Visual polish pass (v0.2.0): phase-based lighting (sun/sky/ambient per shift phase), street lamps with night-mode bulbs and omni lights, glowing marquee signage, crosswalk, planters and low-poly street trees, NPC height/brightness variety, denser routine traffic — day and late-night screenshot sets
|
|
||||||
- Assets pass: authored textures in `assets/textures/` (brick facade, concrete, asphalt, awning, skyline, vignette, HUD icons, access-card art) generated by `tools/generate_textures.py`, soft sun shadows, humanoid NPC rigs with archetype props + procedural walk bob and attention head-turn, inspection artifact cards, escalation vignette, mode icons, and a body-camera overlay (REC + shift clock)
|
|
||||||
|
|
||||||
Not yet implemented: body-camera replay timeline, editor-based GUT integration, and crowd performance profiling/LOD (see `TODO.md`).
|
|
||||||
|
|
||||||
### Smoke Test Checklist
|
|
||||||
|
|
||||||
1. `godot --path .` starts the game on the venue menu without script errors.
|
|
||||||
2. Choosing a venue (click or ↑/↓ + Enter) starts that venue's shift and hides the menu.
|
|
||||||
3. The guard moves and looks around; the building, steps, and sidewalk are visible.
|
|
||||||
4. The guard's camera sits at eye height and the guard stands on the landing, not inside the building.
|
|
||||||
5. Walking into the steps carries the guard up onto the landing (steps have colliders).
|
|
||||||
6. The skyline backdrop shows silhouettes against the sky, not an opaque black slab.
|
|
||||||
7. The `STEP LINE` marquee is readable from the sidewalk.
|
|
||||||
8. NPCs spawn and approach the entrance during the first minutes.
|
|
||||||
9. Looking at an NPC inside the focus cone shows a gold head ring, the person's name, and the HUD target line.
|
|
||||||
10. `Space` or left click opens the action wheel; `A`/`D` highlight a slice, `Space` confirms it, `Esc`/right click cancels without a log entry.
|
|
||||||
11. `E`/`C` open the inspection/wand panels from the wheel and freeze movement until they close; walking never fires the wand.
|
|
||||||
12. Pressing `Q` produces a reaction log entry and reputation feedback.
|
|
||||||
13. `E` opens access inspection showing venue-correct artifacts and policy result; `A`/`D` record a decision.
|
|
||||||
14. `6` opens the assistance form; submitting shows dispatch feedback.
|
|
||||||
15. `R` opens the emergency form; the button must be held ~1.6s or the call cancels.
|
|
||||||
16. `G` toggles the flashlight (indicator appears in the HUD); `L` toggles the door lock (indicator turns red when locked).
|
|
||||||
17. `F` toggles the debug overlay with NPC states and shift stats.
|
|
||||||
18. The wheel's `Look` slice is eye contact (Level 1) and logs as its own action.
|
|
||||||
19. `Reason` asks why the person is there and logs their `dialogue_lines` reply.
|
|
||||||
20. `Recognize` records the guard's recognition as access evidence; pressing it twice says so.
|
|
||||||
21. `Warn` turns the body camera on (REC overlay appears); `B` toggles it manually.
|
|
||||||
22. `Block` moves the guard between the person and the door.
|
|
||||||
23. The shift review reports the incident count, including interactions left unresolved.
|
|
||||||
24. Escalating on someone (or a responder arriving) makes idle bystanders stop and face the incident.
|
|
||||||
25. Repeated alarms panic the crowd and nearby people clear away from the door instead of standing around.
|
|
||||||
26. `Esc` with nothing open pauses the shift; resume / restart / settings / quit-to-menu all work, and settings changes persist across a restart.
|
|
||||||
27. NPCs route around the building using the navigation mesh (no stuck-at-wall behavior during a smoke run).
|
|
||||||
28. An unjustified emergency call (calm person, no weapon) logs an investigation and costs reputation.
|
|
||||||
29. After an evacuation outcome the door is locked and NPCs are leaving the entrance.
|
|
||||||
30. 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,46 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
Godot 4.3 prototype. Release **v0.2.5** is tagged. Headless suite **113/113**, plus
|
Work is grouped by theme. Each item should be small enough to complete and verify on its own;
|
||||||
`smoke_session_shell.gd`, `smoke_plan_verbs.gd`, `smoke_crowd.gd`, `smoke_emergency.gd`, and the
|
see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these plans.
|
||||||
windowed focus/radial smoke.
|
|
||||||
|
|
||||||
Host `REQUIRE_SIGNIN_VIEW` stays deferred.
|
## Forward+ desktop preset and a volumetric flashlight (v0.7.1)
|
||||||
|
|
||||||
## Body-camera replay
|
The Compatibility renderer has no volumetrics, so the flashlight is a tight pool of light with
|
||||||
|
no visible shaft. A second desktop build can render it properly without touching the CI/default
|
||||||
|
path.
|
||||||
|
|
||||||
- [ ] Shift review: a timeline of recorded incident events (ADR-0005 already stores them).
|
- [ ] Write the preset as an `override.cfg` (Godot reads it beside the binary/project) that sets
|
||||||
Scrub or step through; no live 3D replay required in the first version.
|
`rendering/renderer/rendering_method="forward_plus"`, enables `volumetric_fog_*` and SDFGI,
|
||||||
|
and keeps the Compatibility build as the default (`project.godot` unchanged).
|
||||||
|
- [ ] Gate the effect in code: only give the flashlight a volumetric component when the renderer
|
||||||
|
is Forward+ (`RenderingServer.get_rendering_device() != null`), so Web/CI builds are
|
||||||
|
untouched and the existing headless tests keep passing.
|
||||||
|
- [ ] Tune `light_volumetric_fog_energy`/`fog_density` for the LATE phase and re-check the
|
||||||
|
entrance so the shaft does not wash the facade again (the reason the wide cone was cut).
|
||||||
|
- [ ] A/B the two renderers with `capture_realism.gd` (same stages, two `--out` dirs) and record
|
||||||
|
the diff + judge scores in `docs/screens/realism/<date>-forward-plus/`.
|
||||||
|
- [ ] Add the preset to `tools/build_releases.sh` and `docs/RELEASING.md` as an optional
|
||||||
|
"desktop HQ" artifact, and note the runtime requirement (Vulkan driver) in `README.md`.
|
||||||
|
- [ ] Test: a headless run must still boot when Forward+ is unavailable (the preset must degrade
|
||||||
|
to Compatibility instead of failing to start).
|
||||||
|
|
||||||
## NPC theater
|
## Realism follow-ups (deferred)
|
||||||
|
|
||||||
Intents `sit` / `photo` / `film` / `perform` already pick waypoints. ADR-0010 rig (face/hair)
|
- [ ] Re-run the screen-design judge for `12-joke-shop` / `13-studio-arrival` when
|
||||||
is in the working tree. Bodies still idle-stand at those spots.
|
`OPENROUTER_API_KEY` is valid; the 2026-11-01 attempt failed with an invalid-key HTTP 400.
|
||||||
|
- [ ] Capture a hooded/winter NPC close-up once a scenario spawns types 5-7 (the explicit
|
||||||
|
`--portrait-type` override is already in `capture_realism.gd`).
|
||||||
|
- [ ] Human-authored tree/hedge silhouettes: the whole scene now reads well except the foliage,
|
||||||
|
which is still flat-shaded clumps (`_shrub()`/`_tree()` in `tools/blender_assets.py`).
|
||||||
|
Only worth doing if a higher-poly organic builder is added to the library.
|
||||||
|
- [ ] Camera height: the concept frames are shot at ~1.55 m with a slightly wider lens; the game
|
||||||
|
camera sits at 1.7 m. Decide whether the first-person height should match the art direction
|
||||||
|
or stay at the current gameplay value.
|
||||||
|
|
||||||
- [ ] Sit pose at `steps_end`. Photo: brief raise of the influencer/tourist prop. Performer
|
## Future ideas (optional, not scheduled)
|
||||||
stays put and plays the hat/sign.
|
|
||||||
|
|
||||||
## Audio
|
- [ ] Live 3D body-camera replay (the timeline is text-only today).
|
||||||
|
- [ ] Additional venues or seasonal events authored from the new `.tres` files.
|
||||||
`AudioFeedback` is sine beeps. DONE called that "crowd ambience"; it is not.
|
- [ ] 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
|
||||||
- [ ] Loop a low crowd bed that rises with `CrowdSystem.mood`.
|
`assets/models/generated/<prop>.glb` contract.
|
||||||
- [ ] Radio open/close crackle on the existing radio panel, still headless-safe (no-op without
|
|
||||||
an audio server).
|
|
||||||
|
|
||||||
## Accessibility
|
|
||||||
|
|
||||||
- [ ] Escalation colors that still read in grayscale (shape or label, not hue alone).
|
|
||||||
- [ ] HUD font scale in settings.
|
|
||||||
|
|
||||||
## Scenario content still missing vs PLAN lists
|
|
||||||
|
|
||||||
- [ ] Joke Shop: guest on the reservation list without ID; borrowed/mismatched ID; guest
|
|
||||||
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)
|
|
||||||
|
|
||||||
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).
|
|
||||||
- [ ] **Remaining host setting**: instance-wide `[service] REQUIRE_SIGNIN_VIEW=true` makes
|
|
||||||
anonymous browsing/downloads redirect to login. Host admin: `REQUIRE_SIGNIN_VIEW = false`
|
|
||||||
under `[service]` in `app.ini`, restart Gitea. Alternative: mirror downloads.
|
|
||||||
|
|
||||||
## Open Polish
|
|
||||||
|
|
||||||
- [ ] Keep `%` placeholders stable in `Strings.fmt` when adding keys (positional formatting).
|
|
||||||
|
|
||||||
## Screen design references (QA tooling)
|
|
||||||
|
|
||||||
- [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).
|
|
||||||
- [ ] 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)
|
|
||||||
|
|
||||||
- [ ] IDE/editor-based GUT integration. Until then the custom headless runner stays.
|
|
||||||
- [ ] Optional later: author venues as `.tres` resources instead of `ScenarioFactory` functions.
|
|
||||||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1007 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1004 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1000 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 954 KiB |
|
After Width: | Height: | Size: 910 KiB |
|
After Width: | Height: | Size: 971 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1021 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 61 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: 30 KiB |
|
After Width: | Height: | Size: 35 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 |