Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d67e14e52 | ||
|
|
2443343f2b | ||
|
|
3e41cda591 | ||
|
|
84bc4137cb | ||
|
|
5dc3ef0f4c | ||
|
|
6f3facff43 | ||
|
|
51f537720f | ||
|
|
08e9044593 | ||
|
|
ad14410337 | ||
|
|
876fb59882 | ||
|
|
979c6ad789 | ||
|
|
1921bfae91 | ||
|
|
78485259b7 | ||
|
|
fef3af44c0 | ||
|
|
bfe3c5a701 | ||
|
|
46f1e41874 | ||
|
|
f48d87c84f | ||
|
|
a9658ecd81 | ||
|
|
b8507df346 | ||
|
|
60bf9a8ef6 | ||
|
|
b51bdc8efe | ||
|
|
a7b2852253 | ||
|
|
4bb2896819 | ||
|
|
f5b8ef2e70 | ||
|
|
0044f5f065 | ||
|
|
4bc6390e9f | ||
|
|
fb3da52438 | ||
|
|
d7220c1a0a | ||
|
|
31574b8d82 | ||
|
|
b60f67548d | ||
|
|
a57b86cd48 | ||
|
|
dfe5056303 | ||
|
|
58cf12fd1d | ||
|
|
7150bd7673 | ||
|
|
12e70357c5 | ||
|
|
7dc3d3c873 | ||
|
|
cd5e0e0ff7 | ||
|
|
8ce62e7556 | ||
|
|
24c939d388 | ||
|
|
48082c3dfa | ||
|
|
03c355e7c1 | ||
|
|
15ab8c686d | ||
|
|
ab9c67d266 | ||
|
|
7c1a4bfd69 | ||
|
|
e7ee177c31 | ||
|
|
c1cd4898e8 | ||
|
|
e805a91ad3 | ||
|
|
7315166361 | ||
|
|
f6aa26e6ca | ||
|
|
26e6dd6413 | ||
|
|
8f65751975 | ||
|
|
9692f262f1 | ||
|
|
98c31fe44c | ||
|
|
daf0926d91 | ||
|
|
f71a36faa1 | ||
|
|
37f925c7ad | ||
|
|
74dc1fdc1d | ||
|
|
3f335481d6 | ||
|
|
af923b1483 | ||
|
|
3a74cedee0 | ||
|
|
844e39246a | ||
|
|
7c48183410 | ||
|
|
64590fe797 | ||
|
|
49bfccc808 | ||
|
|
7401d4bca0 | ||
|
|
e79a37beba | ||
|
|
fbd2ab2cb3 | ||
|
|
9b8f513f9f | ||
|
|
ad12fdad7e | ||
|
|
87612f2057 | ||
|
|
ffd98daf08 | ||
|
|
f811e6054d | ||
|
|
9d37e0a1c5 | ||
|
|
883626a067 | ||
|
|
b5d4d5fb87 | ||
|
|
fac9b002c3 | ||
|
|
aa51ee331e | ||
|
|
86332cd401 | ||
|
|
76eb5e3a79 | ||
|
|
f4a53e2d4f | ||
|
|
806fe78688 | ||
|
|
6a3066d763 | ||
|
|
f4cce28454 | ||
|
|
7d39523e49 | ||
|
|
7e084192ce | ||
|
|
6c367e8c7e |
No files matched your search
@@ -17,12 +17,13 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Godot 4.3
|
- name: Install Godot 4.7.2
|
||||||
run: |
|
run: |
|
||||||
curl -sL -o /tmp/godot.zip \
|
curl -sL -o /tmp/godot.zip \
|
||||||
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_linux.x86_64.zip
|
https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_linux.x86_64.zip
|
||||||
unzip -qo /tmp/godot.zip -d /tmp/godot-bin
|
unzip -qo /tmp/godot.zip -d /tmp/godot-bin
|
||||||
sudo install /tmp/godot-bin/Godot_v4.3-stable_linux.x86_64 /usr/local/bin/godot
|
sudo install /tmp/godot-bin/Godot_v4.7.2-stable_linux.x86_64 /usr/local/bin/godot
|
||||||
|
godot --version
|
||||||
|
|
||||||
- name: Import project (build class cache)
|
- name: Import project (build class cache)
|
||||||
run: godot --headless --import
|
run: godot --headless --import
|
||||||
@@ -56,18 +57,24 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Godot 4.3 and export templates
|
- name: Install Godot 4.7.2 and export templates
|
||||||
run: |
|
run: |
|
||||||
curl -sL -o /tmp/godot.zip \
|
curl -sL -o /tmp/godot.zip \
|
||||||
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_linux.x86_64.zip
|
https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_linux.x86_64.zip
|
||||||
unzip -qo /tmp/godot.zip -d /tmp/godot-bin
|
unzip -qo /tmp/godot.zip -d /tmp/godot-bin
|
||||||
sudo install /tmp/godot-bin/Godot_v4.3-stable_linux.x86_64 /usr/local/bin/godot
|
sudo install /tmp/godot-bin/Godot_v4.7.2-stable_linux.x86_64 /usr/local/bin/godot
|
||||||
|
godot --version
|
||||||
|
TEMPLATE_DIR="$HOME/.local/share/godot/export_templates/4.7.2.stable"
|
||||||
|
NEEDED="version.txt icudt_godot.dat linux_release.x86_64 windows_release_x86_64.exe macos.zip web_nothreads_release.zip web_dlink_nothreads_release.zip"
|
||||||
curl -sL -o /tmp/templates.tpz \
|
curl -sL -o /tmp/templates.tpz \
|
||||||
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_export_templates.tpz
|
https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_export_templates.tpz
|
||||||
mkdir -p ~/.local/share/godot/export_templates/4.3.stable
|
rm -rf /tmp/tpl && mkdir -p /tmp/tpl
|
||||||
cd /tmp && rm -rf tpl && mkdir tpl && unzip -qo templates.tpz -d tpl
|
unzip -qo /tmp/templates.tpz -d /tmp/tpl
|
||||||
cp tpl/templates/* "$HOME/.local/share/godot/export_templates/4.3.stable/" 2>/dev/null || true
|
mkdir -p "$TEMPLATE_DIR"
|
||||||
ls "$HOME/.local/share/godot/export_templates/4.3.stable" | head -5
|
for file in $NEEDED; do
|
||||||
|
cp "/tmp/tpl/templates/$file" "$TEMPLATE_DIR/$file"
|
||||||
|
done
|
||||||
|
ls "$TEMPLATE_DIR" | head -10
|
||||||
|
|
||||||
- name: Import project
|
- name: Import project
|
||||||
run: godot --headless --import
|
run: godot --headless --import
|
||||||
@@ -114,7 +121,7 @@ jobs:
|
|||||||
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
|
# An explicit repo secret wins. Without one, publish_release.sh reuses
|
||||||
# the local git extraheader that actions/checkout configures for the job
|
# the local git extraheader that actions/checkout configures on the job
|
||||||
# (the runner's own credentials), so tag builds still publish.
|
# (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 }}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ Thumbs.db
|
|||||||
*.new
|
*.new
|
||||||
|
|
||||||
# Logs and temp
|
# Logs and temp
|
||||||
|
tmp/
|
||||||
*.log
|
*.log
|
||||||
user://
|
user://
|
||||||
# CI runner workflow logs (written into the checkout by the Gitea runner)
|
# CI runner workflow logs (written into the checkout by the Gitea runner)
|
||||||
@@ -57,6 +58,10 @@ assets/models/generated/npc_base_*_normal.png
|
|||||||
|
|
||||||
# Local AppImage extraction
|
# Local AppImage extraction
|
||||||
squashfs-root/
|
squashfs-root/
|
||||||
|
|
||||||
|
# Local agent skills (installed per machine from the Blender Skills Pack; not part of
|
||||||
|
# the game, tests or release artifacts, and never shipped)
|
||||||
|
.agents/
|
||||||
# Blender asset library: per-asset Cycles renders are local review output (the
|
# Blender asset library: per-asset Cycles renders are local review output (the
|
||||||
# committed review artefacts are docs/blender_previews/sheet_*.jpg), and Godot
|
# committed review artefacts are docs/blender_previews/sheet_*.jpg), and Godot
|
||||||
# extracts the GLB-embedded textures next to the models on import.
|
# extracts the GLB-embedded textures next to the models on import.
|
||||||
@@ -66,10 +71,14 @@ assets/models/blender/**/*.png
|
|||||||
assets/models/blender_src/*.blend1
|
assets/models/blender_src/*.blend1
|
||||||
# Editable Blender sources are developer-only; keep the .gdignore marker tracked.
|
# Editable Blender sources are developer-only; keep the .gdignore marker tracked.
|
||||||
assets/models/blender_src/*.blend
|
assets/models/blender_src/*.blend
|
||||||
# Realism pass (tools/scene_concepts.py): the candidate pool and per-run captures
|
# Realism pass (tools/scene_concepts.py): candidate pools, per-run captures and
|
||||||
# stay local; the validated picks are tracked under game_ready/v2/picks/ (ADR-0014),
|
# generated reports stay local; validated picks are tracked under
|
||||||
# and each run commits its report.{json,md} + sheet.jpg. Concept references are
|
# game_ready/v2/picks/ (ADR-0014). Concept references are committed in
|
||||||
# committed in docs/screens/concepts/.
|
# docs/screens/concepts/, but generated QA lives outside the repository.
|
||||||
assets/textures/ai/game_ready/v2/*
|
assets/textures/ai/game_ready/v2/*
|
||||||
!assets/textures/ai/game_ready/v2/picks/
|
!assets/textures/ai/game_ready/v2/picks/
|
||||||
docs/screens/realism/**/*.png
|
docs/screens/realism/
|
||||||
|
|
||||||
|
## Local pi agent session data
|
||||||
|
pi-session-*.html
|
||||||
|
pi-session-*.jsonl
|
||||||
@@ -19,10 +19,84 @@ The project should be developed in small, reviewable steps. Agents must keep pla
|
|||||||
- `docs/architecture.md` — The arc42-style architecture documentation (goals, constraints, building blocks, runtime views, decisions, risks).
|
- `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/gameplay.md` — Gameplay systems, venues, and the escalation model.
|
||||||
- `docs/asset-tooling.md` — Asset pipelines, model tools, and screenshot gates.
|
- `docs/asset-tooling.md` — Asset pipelines, model tools, and screenshot gates.
|
||||||
|
- `docs/npc-model-sourcing.md` — Character-source evaluation, licence rules and the character-development plan.
|
||||||
- `docs/getting-started.md` — Setup, run/test commands, and the controls table.
|
- `docs/getting-started.md` — Setup, run/test commands, and the controls table.
|
||||||
- `docs/smoke-checklist.md` — The manual smoke-test checklist.
|
- `docs/smoke-checklist.md` — The manual smoke-test checklist.
|
||||||
- `docs/adr/` — Architecture Decision Records.
|
- `docs/adr/` — Architecture Decision Records.
|
||||||
|
|
||||||
|
## Priority and Versioning
|
||||||
|
|
||||||
|
`TODO.md` is the priority source. The active order is:
|
||||||
|
|
||||||
|
1. unversioned development tracks (currently 3D model re-evaluation, character development and
|
||||||
|
local map development);
|
||||||
|
2. versioned release milestones, in version order;
|
||||||
|
3. future/unversioned candidates and deferred work.
|
||||||
|
|
||||||
|
Do not assign a version number to an unversioned track until its scope is accepted into a
|
||||||
|
release. When a version is assigned, it must be the next semantic version that matches the
|
||||||
|
scope (major/minor/patch), not a placeholder.
|
||||||
|
|
||||||
|
## Local Blender skills (development aid)
|
||||||
|
|
||||||
|
The MIT `blender-skills` pack is installed locally at `.agents/skills/blender-skills/`
|
||||||
|
(94 skills plus a shared `references/` folder). It is **gitignored on purpose**: it is not
|
||||||
|
game content, not part of the build, tests, CI or release packages, and every contributor
|
||||||
|
may or may not have it. The agent runtime (Pi) gates project `.agents/skills` behind project
|
||||||
|
trust, so the trust prompt must be accepted before the skills load; `/skill:blender-director`
|
||||||
|
forces one.
|
||||||
|
|
||||||
|
Use them when a task touches `tools/blender_assets.py`, `tools/npc_pipeline.py`,
|
||||||
|
`tools/bake_ao.py`, `assets/models/blender/**`, or Blender-authored venue/prop geometry.
|
||||||
|
|
||||||
|
### Routing
|
||||||
|
|
||||||
|
Start with `blender-director` for anything multi-discipline, then load only the
|
||||||
|
specialists the task needs:
|
||||||
|
|
||||||
|
| Work | Skills |
|
||||||
|
| --- | --- |
|
||||||
|
| Plan/route a Blender task, reference photo match | `blender-director` |
|
||||||
|
| Character mesh, face, hands, proportions | `character-artist`, `character-archetypes`, `retopology` |
|
||||||
|
| Skeleton, weights, pose/contract rigging | `rigging` |
|
||||||
|
| UV layout, texel density, UDIM | `uv-workflow` |
|
||||||
|
| Bakery, atlas, normal/AO/ORM maps | `texture-workflow` |
|
||||||
|
| PBR materials and shading | `materials` |
|
||||||
|
| Screenshot-driven material/light iteration | `lookdev` |
|
||||||
|
| Triangle/vertex budgets, naming, scene hygiene | `asset-optimization` |
|
||||||
|
| LOD tiers and impostors | `lod-pipeline` |
|
||||||
|
| Collision shapes and proxies | `collision-proxy` |
|
||||||
|
| GLB export for Godot | `godot-export`, `export-pipeline` |
|
||||||
|
| Gear, props, street furniture | `prop-artist`, `hard-surface` |
|
||||||
|
| Trees, hedges, foliage cards | `vegetation-artist` |
|
||||||
|
| Facades, venues, modular kits, dressing | `environment-artist`, `archviz`, `set-dressing`, `geometry-nodes` |
|
||||||
|
| Lighting, framing, final render/composite | `lighting`, `camera-cinematography`, `rendering`, `compositing` |
|
||||||
|
| Style direction (stylized / low-poly / realistic) | `stylized-style`, `lowpoly-style`, `realistic-style` |
|
||||||
|
| Ship/no-ship review | `qa-review` |
|
||||||
|
|
||||||
|
The same skills cover the local map track (procedural geometry, facade kits, OSM massing,
|
||||||
|
splat reference renders) and the NPC roster work.
|
||||||
|
|
||||||
|
### Project rules that override a skill
|
||||||
|
|
||||||
|
1. **The project contract wins.** `npc_base` mesh node, the 13 contract bones at their rest
|
||||||
|
positions, five tint slots, front on Blender −Y / Godot +Z, grounded feet, 8,000
|
||||||
|
triangles / 8,000 glTF vertices per character, 1,500 per gear, 4,000 per prop. A skill
|
||||||
|
may recommend another topology, naming or budget scheme; do not adopt it unless the
|
||||||
|
change is explicitly planned and the contract, tests and docs are updated together.
|
||||||
|
2. **There is no Blender MCP here.** The skills assume MCP tools; this project runs Blender
|
||||||
|
as headless flatpak scripts (`tools/blender_assets.py`, `tools/npc_pipeline.py`). Translate
|
||||||
|
MCP steps into script changes or CLI runs. Never add an MCP dependency to the build, CI or
|
||||||
|
runtime.
|
||||||
|
3. **CI stays offline and skill-free.** Skills are local guidance only; every gate must still
|
||||||
|
work from a fresh checkout with committed `.glb`/`.png`/`.tres` files and Godot.
|
||||||
|
4. **No new asset source without the licence gate.** Skills may suggest reference packs or
|
||||||
|
generated meshes; ADR-0015 (CC0, or CC-BY with attribution in `CREDITS.md`) and the
|
||||||
|
provenance rules in `docs/npc-model-sourcing.md` still apply.
|
||||||
|
5. **Evidence, not vibes.** After a skill-informed change, run `blender_assets.py inspect`,
|
||||||
|
`audit` and `fit` plus the relevant capture/smoke gates, and say in the handoff which
|
||||||
|
skills informed the change and what the measurements were.
|
||||||
|
|
||||||
## Required Workflow
|
## Required Workflow
|
||||||
|
|
||||||
### 1. Start by Inspecting the Repository
|
### 1. Start by Inspecting the Repository
|
||||||
@@ -44,7 +118,16 @@ Do not assume that a task is unimplemented without checking the code and documen
|
|||||||
All planned implementation work must be represented in `TODO.md`.
|
All planned implementation work must be represented in `TODO.md`.
|
||||||
|
|
||||||
- Use headings to group related work into sections.
|
- Use headings to group related work into sections.
|
||||||
- Use checkbox items for each concrete task.
|
- Use level-2 headings (`##`) for milestones or stable themes. A milestone should state its goal
|
||||||
|
and an explicit exit criterion; a theme groups small related work that does not need a
|
||||||
|
milestone.
|
||||||
|
- Order level-2 sections by current priority: unversioned development tracks first, then
|
||||||
|
versioned release milestones in version order, then future/deferred work. Do not assign a
|
||||||
|
version number to an unversioned track until it is scheduled for a release.
|
||||||
|
- Use level-3 headings (`###`) for workstreams inside a milestone (for example, `0.8.1
|
||||||
|
First-shift tutorial`). Keep the hierarchy to three levels: milestone → workstream → checkbox.
|
||||||
|
- Use checkbox items for each concrete task, normally under a level-3 workstream. Direct
|
||||||
|
checkboxes under a level-2 section are acceptable for small, single-workstream sections.
|
||||||
- Keep tasks small enough to complete and review independently.
|
- 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 `[-]`
|
- When starting a task, claim it for the agent workflow by changing `[ ]` to `[-]`. Keep it `[-]`
|
||||||
@@ -61,29 +144,55 @@ Recommended format:
|
|||||||
```md
|
```md
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
## Core Gameplay
|
## Milestone 0.8.0 — First hour and content depth
|
||||||
|
|
||||||
- [ ] Add player movement controls.
|
### 0.8.1 First-shift tutorial
|
||||||
- [-] Implement NPC approach behavior. (claimed; implementation/verification in progress)
|
|
||||||
- [x] Define escalation levels.
|
- [x] Write the tutorial beat sheet.
|
||||||
|
- [-] Implement the tutorial state machine. (claimed; implementation/verification in progress)
|
||||||
|
- [ ] Add the tutorial smoke test.
|
||||||
|
|
||||||
|
### 0.8.2 Campaign shell
|
||||||
|
|
||||||
|
- [ ] Define campaign data.
|
||||||
|
- [ ] Persist campaign progress.
|
||||||
|
|
||||||
|
**Exit:** a first-time player completes a shift without external documentation.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [ ] Document the test procedure in `README.md`.
|
- [ ] Document the test procedure in `README.md`.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Recommended `DONE.md` format: a `# DONE` heading, followed by each moved level-2 section
|
||||||
|
unchanged, with a completion date line under each section heading.
|
||||||
|
|
||||||
|
Order the actual file as described in "Priority and Versioning": unversioned development tracks
|
||||||
|
first (for example character and local-map development), then versioned milestones in version
|
||||||
|
order, then future/deferred work.
|
||||||
|
|
||||||
### 3. Move Completed Sections to `DONE.md`
|
### 3. Move Completed Sections to `DONE.md`
|
||||||
|
|
||||||
When every task in a `TODO.md` section is complete:
|
`TODO.md` uses nested sections. Apply these completion rules:
|
||||||
|
|
||||||
1. Verify the work and update the checkbox items to `[x]`.
|
1. **Level-3 workstream:** verify the work, mark every checkbox `[x]`, and add a short completion
|
||||||
2. Move the entire completed section, including its heading and checklist, to `DONE.md`.
|
note. Leave the completed workstream under its parent milestone; do not move an isolated
|
||||||
3. Remove that section from `TODO.md`.
|
level-3 section to `DONE.md`.
|
||||||
4. Add a short completion note or date when useful.
|
2. **Level-2 milestone/theme:** it is complete only when its direct checkboxes and **all** of its
|
||||||
|
level-3 workstreams are complete. Verify the whole milestone, record its capabilities in
|
||||||
|
`docs/development-status.md` when it represents shipped functionality, and write the matching
|
||||||
|
`CHANGELOG.md` entry.
|
||||||
|
3. Move the **entire level-2 section**, including all level-3 headings and checklists, to
|
||||||
|
`DONE.md`. Remove it from `TODO.md` and add a completion date/note.
|
||||||
|
4. **Planning/overview sections** such as a gap analysis, roadmap overview, dependencies or
|
||||||
|
deferred ideas are not completion milestones. Do not move them merely because their checkboxes
|
||||||
|
are empty or complete; update or supersede them as the roadmap changes.
|
||||||
|
5. Do not move partially completed milestones. Leave them in `TODO.md` until every nested task is
|
||||||
|
complete, or split the unfinished work into a new smaller level-2 milestone first.
|
||||||
|
|
||||||
Do not move partially completed sections. Leave them in `TODO.md` until all of their tasks are complete, or split the section into smaller sections first.
|
When a completed milestone represents shipped functionality, also record the new capability in
|
||||||
|
`docs/development-status.md` so the implemented-features list stays accurate, and write a matching
|
||||||
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).
|
entry in `CHANGELOG.md` (see "Maintain `CHANGELOG.md`" below).
|
||||||
|
|
||||||
### 4. Maintain `MEMORY.md`
|
### 4. Maintain `MEMORY.md`
|
||||||
|
|
||||||
@@ -149,12 +258,14 @@ Update `README.md` whenever setup instructions, commands, project structure, or
|
|||||||
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.
|
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.
|
- 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.
|
- Follow Semantic Versioning (`major.minor.patch`): bump major only for changes that remove or break an existing player-facing feature, save format, or config contract, or that replace a system listed as a core pillar in PLAN.md; bump minor for new user-facing features; bump patch for fixes and small polish.
|
||||||
|
- A version increment is not complete until its `CHANGELOG.md` entry, `config/version` update and
|
||||||
|
matching local release build have all been produced and verified.
|
||||||
- Group each entry under `Added`, `Changed`, `Fixed`, and `Removed` headings, and date every entry.
|
- 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.
|
- 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.
|
- Add the entry when its work ships. If the version number is not yet known, record it under `Unreleased` as a staging entry; moving it into a numbered version is a version increment and is complete only when the `config/version` update and matching local release build have also been produced and verified (see "Commit, Build and Version Discipline").
|
||||||
|
|
||||||
When a full level-2 section in `TODO.md` is implemented, completed, and moved to `DONE.md`:
|
When a full executable level-2 milestone/theme in `TODO.md` is implemented, completed, and moved to `DONE.md`:
|
||||||
|
|
||||||
1. Verify the work and mark every checkbox in the section `[x]`.
|
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.
|
2. Use the section heading and its completed items to describe the changes and write a `CHANGELOG.md` entry under the next appropriate version.
|
||||||
@@ -172,14 +283,42 @@ For each task:
|
|||||||
4. Run the relevant tests, checks, or manual verification.
|
4. Run the relevant tests, checks, or manual verification.
|
||||||
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`.
|
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 (after recording its `CHANGELOG.md` entry).
|
7. Keep completed level-3 workstreams in place until the parent milestone is complete; then move the entire level-2 milestone to `DONE.md` after recording its `CHANGELOG.md` entry.
|
||||||
8. Commit the changes to the version control system with a descriptive message.
|
8. Commit each coherent, verified change as it is completed. Do not wait until a whole
|
||||||
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.
|
milestone is finished; keep commits small and descriptive and push them when the branch is
|
||||||
10. Push the committed changes to the shared repository if applicable.
|
shareable.
|
||||||
11. Note the new version or tag created in the version control system in any relevant documentation or handoff summary.
|
9. If the change is a release (a major, minor, or patch version increment), follow the Release
|
||||||
|
Procedure in "Commit, Build and Version Discipline".
|
||||||
|
10. Push committed changes to the shared repository if applicable.
|
||||||
|
11. Note the new version, build artifacts or tag in the relevant documentation and 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.
|
||||||
|
|
||||||
|
### 9. Commit, Build and Version Discipline
|
||||||
|
|
||||||
|
- **Commit often.** Make a commit after each coherent change or verified task, not only when a
|
||||||
|
milestone or release is finished. Keep the working tree clean between tasks where possible.
|
||||||
|
- **Push regularly.** Push shareable commits to the shared repository; do not accumulate a long
|
||||||
|
local-only history.
|
||||||
|
- **Build every version.** Every major, minor and patch increment follows the Release Procedure
|
||||||
|
below; a changelog entry alone is not a release.
|
||||||
|
- **CI is a second gate, not a substitute.** Tag builds remain automated, but the local build is
|
||||||
|
the required pre-tag check for every version.
|
||||||
|
- **Unversioned tracks stay unversioned.** Character, map and other development tracks do not get
|
||||||
|
a placeholder version; the version is assigned when the work is scoped into a release.
|
||||||
|
|
||||||
|
**Release Procedure** — follow these steps in order for every major, minor, or patch version
|
||||||
|
increment:
|
||||||
|
|
||||||
|
1. Update `config/version`.
|
||||||
|
2. Write the `CHANGELOG.md` entry for the version.
|
||||||
|
3. Run the local release build (`tools/build_releases.sh`).
|
||||||
|
4. Verify the build artifacts and checksums.
|
||||||
|
5. Commit the changes.
|
||||||
|
6. Tag the version.
|
||||||
|
7. Push the commit and tag.
|
||||||
|
|
||||||
## Collaboration Rules
|
## Collaboration Rules
|
||||||
|
|
||||||
- Prefer clear, reversible changes over broad rewrites.
|
- Prefer clear, reversible changes over broad rewrites.
|
||||||
@@ -188,6 +327,8 @@ Avoid unrelated refactors unless they are required for the task or explicitly ap
|
|||||||
- Do not claim a task is complete without verification.
|
- Do not claim a task is complete without verification.
|
||||||
- Keep documentation synchronized with the implementation.
|
- Keep documentation synchronized with the implementation.
|
||||||
- Mention files changed and checks run in handoff summaries.
|
- Mention files changed and checks run in handoff summaries.
|
||||||
|
- Commit coherent increments as you go; do not wait until the end of a milestone.
|
||||||
|
- Follow the Release Procedure in "Commit, Build and Version Discipline" whenever a major, minor or patch version is incremented.
|
||||||
- Leave follow-up work as checkbox items in `TODO.md` rather than hiding it in prose.
|
- Leave follow-up work as checkbox items in `TODO.md` rather than hiding it in prose.
|
||||||
- If blocked, document the blocker in `MEMORY.md` and add a corresponding task to `TODO.md`.
|
- If blocked, document the blocker in `MEMORY.md` and add a corresponding task to `TODO.md`.
|
||||||
- Avoid overwriting another contributor’s work; inspect recent changes before editing shared files.
|
- Avoid overwriting another contributor’s work; inspect recent changes before editing shared files.
|
||||||
@@ -199,6 +340,10 @@ Before handing off work, an agent must run every check below that applies to the
|
|||||||
- Run automated tests.
|
- Run automated tests.
|
||||||
- Run formatting, linting, or type checks.
|
- Run formatting, linting, or type checks.
|
||||||
- Start the game or application and perform a basic smoke test.
|
- Start the game or application and perform a basic smoke test.
|
||||||
|
- For 3D/Blender changes: `blender_assets.py inspect`, `audit` and `fit`, the relevant capture
|
||||||
|
gate, and a note of which local skills informed the change.
|
||||||
|
- If the local Blender skills are not installed, state "no local skills used" in the handoff
|
||||||
|
and list the skill-free checks that were run instead.
|
||||||
- Check that documentation and task status reflect the actual state.
|
- Check that documentation and task status reflect the actual state.
|
||||||
- Review the final diff for accidental changes or unfinished markers.
|
- Review the final diff for accidental changes or unfinished markers.
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,235 @@ All notable user-facing changes to Gig Line. The format follows
|
|||||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
|
||||||
Semantic Versioning.
|
Semantic Versioning.
|
||||||
|
|
||||||
|
## [0.10.0-rc.1] — 2026-11-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Prepared the release-candidate packages for Linux, Windows, macOS, Web and the
|
||||||
|
optional Desktop HQ zip. The Web/HQ archives now include the credits, privacy,
|
||||||
|
accessibility and known-limitations pages, and release publishing writes
|
||||||
|
SHA-256 checksums and rolls back partial uploads.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Corrupt or partial save files now fall back to safe defaults without logging a
|
||||||
|
parse error, and a failed atomic replace leaves the previous valid settings
|
||||||
|
intact. Added migration coverage for 0.8-era profiles and 0.9.0 checkpoints
|
||||||
|
across all three venues.
|
||||||
|
|
||||||
|
## [0.9.6] — 2026-11-02
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The UI, model and texture passes are now integrated into a verified checkpoint
|
||||||
|
build. The gallery, NPC portraits, realism captures, asset audits, smoke
|
||||||
|
matrix, 24-NPC budget and Web/Desktop HQ packages were regenerated and
|
||||||
|
re-checked; no gameplay or content changes were introduced.
|
||||||
|
|
||||||
|
## [0.9.5] — 2026-11-02
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NPC albedo, normal and ORM maps are now 256² instead of 128², giving the
|
||||||
|
face, fabric and gear materials more detail at portrait distance.
|
||||||
|
- NPC fabric maps now repeat four times across the body and skip the baked
|
||||||
|
per-character AO, removing the large blotchy patches from shirts, jackets and
|
||||||
|
trousers at portrait distance.
|
||||||
|
- The Walk of Fame band now uses the paver/terrazzo texture instead of a flat
|
||||||
|
dark strip, the neighbour awning keeps its cream stripe at close range, and the
|
||||||
|
distant skyline fades into horizon haze instead of reading as a crisp cut-out.
|
||||||
|
|
||||||
|
## [0.9.4] — 2026-11-02
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Character meshes now have an 8,000-triangle / 8,000-vertex library budget
|
||||||
|
(up from 6,000 / 5,000) to support the remaining face, grip and clothing
|
||||||
|
detail; the `npc_base` fallback keeps its original 6,000 / 4,000 contract.
|
||||||
|
- Character necks are fuller and shoulders slope from the neck instead of
|
||||||
|
reading as a thin post on a hanger.
|
||||||
|
- Character eyes sit in the socket behind a thin upper lid, and the chin and
|
||||||
|
cheekbones are sculpted into the head instead of reading as separate spheres.
|
||||||
|
- Character torsos taper from the chest to the waist and flare slightly into
|
||||||
|
the hips, so shirts and jackets sit less like straight tubes.
|
||||||
|
- Character hands now have a wider palm, bent fingers with rounded tips and a
|
||||||
|
separate thumb instead of flat bars.
|
||||||
|
- Character noses gained ala wings and a smaller bridge/tip, the mouth is a
|
||||||
|
rounded slot, and the ears have a darker inner concha.
|
||||||
|
- Hand-held items now sit forward in the open palm instead of being partly
|
||||||
|
buried inside the hand.
|
||||||
|
- Shoes and responder boots now have rounded toes and heels instead of reading
|
||||||
|
as flat blocks.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Tourists no longer appear to carry two backpacks: the photo-pose camera bag is
|
||||||
|
hidden except while they are actually taking a picture.
|
||||||
|
- Backpack shoulder straps are now flattened bands that sit outside the chest
|
||||||
|
instead of thin rods partly buried in the torso.
|
||||||
|
|
||||||
|
## [0.9.3] — 2026-11-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Eight committed gallery captures now cover the start menu, pause, settings,
|
||||||
|
shift review, emergency radio, action wheel, crowd panic and responder
|
||||||
|
arrival, so every screen-design reference has a deterministic current-build
|
||||||
|
capture.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- All session, settings, radio, Help, review and timeline action buttons now
|
||||||
|
share explicit normal, hover, pressed, focus and disabled styles. Timeline
|
||||||
|
Previous/Next disable at their boundaries.
|
||||||
|
- Keyboard and gamepad focus rings now wrap through every modal, with explicit
|
||||||
|
default focus and A/B accept/cancel bindings; hidden Continue and disabled
|
||||||
|
timeline controls are skipped.
|
||||||
|
- Esc and right-click now close the top panel consistently, restore the venue
|
||||||
|
menu after menu Settings, and recapture the mouse without leaving the guard
|
||||||
|
frozen. Help no longer opens Pause behind itself.
|
||||||
|
- The HUD target chip grows and caps with the selected font scale, top labels
|
||||||
|
clip safely, and long action-button labels ellipsize instead of pushing panels
|
||||||
|
wider than the screen.
|
||||||
|
- Campaign reset and tutorial replay now share a guarded confirmation dialog;
|
||||||
|
repeated presses cannot stack dialogs or fire the action twice. Emergency
|
||||||
|
hold-to-send still submits once and ignores the release after submission.
|
||||||
|
- NPC reason answers now use role-specific dialogue contexts, so staff, guests,
|
||||||
|
delivery workers, performers and other archetypes answer instead of silently
|
||||||
|
deflecting the Reason verb.
|
||||||
|
- Hollywood and the TV Studio now have door-staff lines for their wristband,
|
||||||
|
ticket, credential and guest-list checks.
|
||||||
|
- Clarified the emergency radio cancellation message, the Ask verb in Help/HUD
|
||||||
|
hints, the start-menu key hint and the campaign-reset confirmation.
|
||||||
|
|
||||||
|
## [0.9.2] — 2026-11-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- A stylized reconstruction of the real El Capitan Entertainment Centre block at
|
||||||
|
6840 Hollywood Boulevard: six Ionic columns, eight stone steps, the Masonic
|
||||||
|
parapet motto and medallions, red event banners, central and side entrances,
|
||||||
|
the El Capitan Theatre and retail neighbours, the Ovation Hollywood arcade,
|
||||||
|
the Walk of Fame and four-lane street markings.
|
||||||
|
- Real-site reference notes and AI art-direction frames for the Hollywood block.
|
||||||
|
- The Joke Shop is now a Sunset Strip comedy-club block based on The Comedy Store at
|
||||||
|
8433 Sunset Blvd: a 27 m black hall under a signature-covered canopy, a circular
|
||||||
|
**THE JOKE SHOP** blade sign, red velvet curtain bays, a west-end entrance, cream
|
||||||
|
and white neighbours, a dry hillside of stucco houses and palms, and a low retail
|
||||||
|
and billboard frontage across Sunset.
|
||||||
|
- Real-site reference notes and AI art-direction frames for the Joke Shop block,
|
||||||
|
including day, night and across-the-street concepts.
|
||||||
|
- A separate day capture stage for the Joke Shop, so the club can be compared in
|
||||||
|
both lighting states.
|
||||||
|
- Completed the UI string migration: start menu, settings, pause, Help, HUD,
|
||||||
|
radio and shift-review labels now come from the English string table.
|
||||||
|
- Separate **Hollywood Boulevard — Day** and **Hollywood Boulevard — Night**
|
||||||
|
rows in the venue menu. Night starts directly in the late phase and keeps the
|
||||||
|
full shift after dark.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The default Hollywood venue is no longer a generic brick entrance; the venue
|
||||||
|
name and policy copy now identify the El Capitan Entertainment Centre.
|
||||||
|
- Night lighting on the landmark uses low-energy practical washes rather than
|
||||||
|
the street-lamp brightness, so the facade reads without blowing out.
|
||||||
|
- The Joke Shop lighting now uses warm sconces, canopy downlights and ground
|
||||||
|
uplights; the hillside stays a dark silhouette at night instead of a lit wall.
|
||||||
|
- The Joke Shop venue copy and gallery captions describe the Sunset Strip block,
|
||||||
|
not the old brick alley.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The Ovation Hollywood sign faces the boulevard instead of rendering mirrored.
|
||||||
|
- Local night lights use linear attenuation instead of constant falloff, so
|
||||||
|
sconces and street lamps pool on the ground and stone rather than washing the
|
||||||
|
whole facade flat and yellow.
|
||||||
|
- The captured HUD clears stale objectives and target prompts between staged
|
||||||
|
screenshots.
|
||||||
|
- The Joke Shop beauty capture closes a stale wand/inspection panel before shooting
|
||||||
|
and frames the entrance and sign instead of a nearby target.
|
||||||
|
- Multiline HUD and wand strings now render real line breaks instead of a literal
|
||||||
|
`\n`; the wand panel also lists the guest's carried items.
|
||||||
|
- Final-context NPC gear fit: authorized-guest lanyards now show their badge in
|
||||||
|
front of the jacket, and responder helmets sit above the eyes instead of across
|
||||||
|
the brow.
|
||||||
|
|
||||||
|
## [0.9.1] — 2026-11-02
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Unified the project, local tooling and CI/release environment on Godot 4.7.2,
|
||||||
|
including matching export templates and project feature metadata.
|
||||||
|
- Evaluated Gitea Actions cache for the Godot binary/templates; disabled it after
|
||||||
|
cache saves timed out or were rejected, pending a healthy host cache service.
|
||||||
|
- Updated release and setup documentation for the 4.7.2 baseline.
|
||||||
|
|
||||||
|
## [0.9.0] — 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Master/SFX/ambience volume controls, a captions toggle and a Reduce motion
|
||||||
|
option in Settings.
|
||||||
|
- A 24-NPC performance budget smoke and expanded audio/accessibility coverage.
|
||||||
|
- Explicit production-quality scope for animation, audio, localization and the
|
||||||
|
Godot 4.3-CI/4.7-local support matrix.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Coat and dress strides are shortened to reduce hem/leg clipping.
|
||||||
|
- Help and the shift-review surface expose the current policy and accessibility
|
||||||
|
settings.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Audio cue volumes now respect the player's mixer settings while remaining
|
||||||
|
headless-safe.
|
||||||
|
|
||||||
|
## [0.8.1] — 2026-11-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Normal radio reports now send on click. The button was wired only to the
|
||||||
|
emergency hold-to-confirm handlers, so a non-emergency press did nothing.
|
||||||
|
|
||||||
|
## [0.8.0] — 2026-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- A first-shift tutorial with contextual prompts, a safe visitor and replay/skip.
|
||||||
|
- A three-venue campaign shell with shift progression and a Continue action.
|
||||||
|
- Resume-from-checkpoint for paused shifts, including NPC, incident and system state.
|
||||||
|
- Easy/normal/hard difficulty presets for spawn pacing, compliance tolerance and
|
||||||
|
report strictness.
|
||||||
|
- A searchable in-game Help overlay with controls and the current venue policy.
|
||||||
|
- Recovery events, contextual NPC dialogue and an eligible-event debug preview.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The shift review now includes venue policy, campaign next steps and continued
|
||||||
|
review controls.
|
||||||
|
- Event scheduling suppresses recently played scripted events and respects the
|
||||||
|
difficulty spawn multiplier.
|
||||||
|
- Save data is written atomically and split into settings, campaign, shift
|
||||||
|
history and versioned checkpoint files.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Missing/older settings migrate through defaults; a partial write cannot
|
||||||
|
replace a valid save.
|
||||||
|
- The HUD debug overlay now refreshes and shows the next eligible events.
|
||||||
|
|
||||||
|
## [0.7.3] — 2026-11-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Mouse look now reads relative motion at the `_input` stage, so a full-screen
|
||||||
|
HUD or focused control cannot swallow it while the cursor is captured.
|
||||||
|
- Mouse capture is restored after window/compositor focus returns, and clicking
|
||||||
|
back into a gameplay window recaptures it instead of opening the radial menu.
|
||||||
|
- The HUD is mouse-transparent; its presentation controls no longer block
|
||||||
|
gameplay mouse input.
|
||||||
|
|
||||||
## [0.7.2] — 2026-11-01
|
## [0.7.2] — 2026-11-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -49,12 +278,6 @@ Semantic Versioning.
|
|||||||
|
|
||||||
## [0.7.0] — 2026-11-01
|
## [0.7.0] — 2026-11-01
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Agent workflow now requires maintaining this changelog for every patch, minor, and major version.
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Distinct venue geometry: the Joke Shop is now a narrow brick alley with a
|
- Distinct venue geometry: the Joke Shop is now a narrow brick alley with a
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Credits and Provenance
|
||||||
|
|
||||||
|
Gig Line is a local, single-player security-shift game built with Godot 4 and
|
||||||
|
GDScript. This file records the asset provenance needed for the release review;
|
||||||
|
it is not a legal opinion or a claim that every provider licence has already
|
||||||
|
been cleared for public distribution.
|
||||||
|
|
||||||
|
## Game, code and design
|
||||||
|
|
||||||
|
- Game design, systems, scenarios, dialogue and UI code: the Gig Line project.
|
||||||
|
- Engine: [Godot Engine 4.7.2](https://godotengine.org/), MIT licence.
|
||||||
|
- The game bundles no third-party fonts; it uses Godot's default theme font.
|
||||||
|
- No third-party music, sound effects or voice recordings are bundled. Crowd
|
||||||
|
ambience and radio cues are generated procedurally at runtime.
|
||||||
|
|
||||||
|
## 3D characters, gear and props
|
||||||
|
|
||||||
|
The character, gear and prop library is generated by project-owned scripts
|
||||||
|
(`tools/blender_assets.py`, `tools/npc_pipeline.py`, `tools/model_pipeline.py`)
|
||||||
|
using Blender. The `.blend` working sources are developer artifacts and are not
|
||||||
|
shipped. The exported `.glb` files and baked AO maps are project assets; they do
|
||||||
|
not embed third-party models or purchased asset packs.
|
||||||
|
|
||||||
|
### Development-only research previews
|
||||||
|
|
||||||
|
Two review sheets under `docs/blender_previews/` compare source candidates for
|
||||||
|
the future NPC variety work. They are development documentation and are not
|
||||||
|
bundled with the game or its release packages. The underlying source models are
|
||||||
|
kept outside version control.
|
||||||
|
|
||||||
|
- [`sheet_npc_sources_research.jpg`](docs/blender_previews/sheet_npc_sources_research.jpg)
|
||||||
|
includes renders of **Quaternius Ultimate Modular Characters** (CC0 1.0,
|
||||||
|
<https://quaternius.com/>), **Kenney Blocky Characters 2.0** (CC0 1.0,
|
||||||
|
<https://kenney.nl/assets/blocky-characters>), and **CesiumMan** and
|
||||||
|
**RiggedFigure** (CC BY 4.0, © 2017 Cesium,
|
||||||
|
<https://github.com/KhronosGroup/glTF-Sample-Assets>).
|
||||||
|
- [`sheet_npc_crowd_prototypes.jpg`](docs/blender_previews/sheet_npc_crowd_prototypes.jpg)
|
||||||
|
contains renders of project-owned generator prototypes.
|
||||||
|
|
||||||
|
The CC-BY preview images are included only as development evidence. Any future
|
||||||
|
shipped derivative of CesiumMan or RiggedFigure must keep the attribution above.
|
||||||
|
|
||||||
|
The venue geometry is authored procedurally in `src/game/world_builder.gd` as a
|
||||||
|
stylized reconstruction. The Joke Shop is a fictional stand-in for the real
|
||||||
|
Sunset Strip block; the game does not claim endorsement by, or an exact replica
|
||||||
|
of, any real venue or tenant.
|
||||||
|
|
||||||
|
## AI-assisted images
|
||||||
|
|
||||||
|
Surface textures, skyline panoramas, UI art and concept references were produced
|
||||||
|
with image-generation calls from the project's tooling. The default model slugs
|
||||||
|
recorded in the source are:
|
||||||
|
|
||||||
|
- `bytedance-seed/seedream-4.5` — `tools/generate_textures.py`
|
||||||
|
- `openai/gpt-5-image-mini` — `tools/asset_pipeline.py`,
|
||||||
|
`tools/model_pipeline.py`, `tools/scene_concepts.py` and
|
||||||
|
`tools/screen_designs.py`
|
||||||
|
- Judging/review calls use the model selected by `tools/openrouter_judge.py` and
|
||||||
|
are development-only; they do not ship in the game.
|
||||||
|
|
||||||
|
The prompts and generation workflow live in those tools. Per-file generation
|
||||||
|
metadata is not stored with every image, so the exact provider, model version and
|
||||||
|
generation date of each committed bitmap cannot be reconstructed from the
|
||||||
|
repository alone. Before a public 0.10.0 release, the provider terms and
|
||||||
|
commercial-use/redistribution rights for those generated outputs must be
|
||||||
|
reviewed and recorded. **This is a release blocker, not an assumed clearance.**
|
||||||
|
|
||||||
|
Reference photographs linked from the venue documents are hosted on Wikimedia
|
||||||
|
Commons and were used for proportion/material study only. They are not included
|
||||||
|
in the game or its release packages. If any future derivative asset copies a
|
||||||
|
reference more directly, its attribution and licence must be added here.
|
||||||
|
|
||||||
|
## Audio
|
||||||
|
|
||||||
|
All runtime audio is procedural (crowd bed, radio crackle and interface cues).
|
||||||
|
There are no external audio files, so there is no separate music/SFX licence to
|
||||||
|
clear. See `docs/accessibility.md` for caption and volume behaviour.
|
||||||
|
|
||||||
|
## Third-party names
|
||||||
|
|
||||||
|
The game is a work of fiction. Real place names and public references are used
|
||||||
|
for orientation and satire/stand-in geography; no real person, venue or brand
|
||||||
|
endorses the game. Review the storefront and trademark language before release.
|
||||||
@@ -1336,3 +1336,700 @@ back when a provider rejects a request.
|
|||||||
records the selected model and ~$0.006 spend.
|
records the selected model and ~$0.006 spend.
|
||||||
- [x] Update `docs/asset-tooling.md`, `MEMORY.md`, `README.md` and `CHANGELOG.md`; the
|
- [x] Update `docs/asset-tooling.md`, `MEMORY.md`, `README.md` and `CHANGELOG.md`; the
|
||||||
public catalogue picks the cheapest image+JSON model (`z-ai/glm-5.3-flash` at run time).
|
public catalogue picks the cheapest image+JSON model (`z-ai/glm-5.3-flash` at run time).
|
||||||
|
|
||||||
|
## Milestone 0.8.0 — First hour and content depth
|
||||||
|
|
||||||
|
Completed 2026-11-01. Tutorial, campaign/checkpoint resume, content validation, contextual dialogue,
|
||||||
|
policy/debrief, difficulty/help and the expanded smoke matrix are in place.
|
||||||
|
|
||||||
|
Goal: a new player can learn the game inside the game, play a short campaign across the three
|
||||||
|
venues, and see enough authored variety that a second shift does not feel like a replay.
|
||||||
|
|
||||||
|
### 0.8.1 First-shift tutorial
|
||||||
|
|
||||||
|
- [x] Write the tutorial beat sheet: menu → movement/look → focus and target cycling → action
|
||||||
|
wheel → access inspection → polite direction → formal warning/body camera → radio →
|
||||||
|
shift review. Eight beats and a safe compliant visitor are specified in
|
||||||
|
[`docs/tutorial.md`](docs/tutorial.md).
|
||||||
|
- [x] Add a tutorial state machine independent of the normal shift FSM. `Tutorial` in
|
||||||
|
`src/game/tutorial.gd` owns the eight beats, accepts only the current beat's action,
|
||||||
|
supports skip/restart and has deterministic headless tests. GameManager/UI wiring starts
|
||||||
|
with the contextual-prompt task.
|
||||||
|
- [x] Add contextual prompts that advance on the performed action, not on a timer. The
|
||||||
|
non-blocking `TutorialOverlay` card updates from `Tutorial.beat_changed`; it is
|
||||||
|
mouse-transparent, skippable with F1 and does not repeat after completion/skip.
|
||||||
|
- [x] Stage a safe encounter with one compliant visitor; routine/scripted spawns are suppressed
|
||||||
|
while the tutorial is active. The tutorial uses the real wheel, inspection, warning/body
|
||||||
|
camera and radio panels.
|
||||||
|
- [x] Persist tutorial completion/skip in `settings.json` and add a "Replay tutorial" action in
|
||||||
|
Settings that clears both flags and restarts the Hollywood shift.
|
||||||
|
- [x] Add localized tutorial strings (`tutorial.*` keys) and caption-ready prompt hooks in the
|
||||||
|
overlay/state table.
|
||||||
|
- [x] Add headless tutorial-state tests and `smoke_tutorial.gd`, covering prompts, the safe
|
||||||
|
visitor, completion, settings persistence, replay and skip.
|
||||||
|
|
||||||
|
Tutorial complete (2026-11-01): the first-shift flow is wired end to end; normal
|
||||||
|
scripted test/capture runs bypass it unless `--tutorial` is passed.
|
||||||
|
|
||||||
|
### 0.8.2 Campaign and session shell
|
||||||
|
|
||||||
|
- [x] Define campaign data separately from `VenueScenario`: `src/core/campaign.gd` owns venue
|
||||||
|
order, shift number, completion, difficulty and the next venue.
|
||||||
|
- [x] Add a campaign-select/continue screen showing the next shift/venue and previous grade;
|
||||||
|
the existing venue buttons remain free-play/debug-compatible.
|
||||||
|
- [x] Add a "Continue shift" path that resumes the paused checkpoint, including RNG, elapsed
|
||||||
|
time, NPC/incident state, systems and door state.
|
||||||
|
- [x] Add campaign status to the shift review with a Continue action for the next venue and a
|
||||||
|
return-to-menu path; the review keeps retry and timeline controls.
|
||||||
|
- [x] Persist campaign state (`campaign.json`) and add a confirmed Reset campaign action in
|
||||||
|
Settings.
|
||||||
|
- [x] Add Campaign unit tests plus `smoke_campaign.gd` covering resume, shift advancement, review
|
||||||
|
continue and help.
|
||||||
|
|
||||||
|
### 0.8.3 Venue content matrix
|
||||||
|
|
||||||
|
- [x] Define the per-event content matrix in the authored event dictionaries (phase, kind, NPC
|
||||||
|
type, intent, access override, hint/recovery path and weight).
|
||||||
|
- [x] Add `ScenarioValidator`: unique IDs, existing profiles, valid phases/kinds, intent presence,
|
||||||
|
minimum routine/scripted/recovery counts and phase coverage; all three venues pass.
|
||||||
|
- [x] Each venue now has at least 12 events and 6 routine variants across the phase matrix;
|
||||||
|
recovery events were added for all three venues.
|
||||||
|
- [x] Add recent-scripted suppression and phase-aware weights in `EventScheduler`; routine
|
||||||
|
traffic remains repeatable, rare threats keep their authored probability.
|
||||||
|
- [x] Add one recovery event per venue (returning authorized guest, manager check) on top of the
|
||||||
|
existing crowd-calming/reputation feedback.
|
||||||
|
- [x] Add `EventScheduler.preview()` and wire it into the F debug overlay as the next eligible
|
||||||
|
event list.
|
||||||
|
- [x] Add content-matrix/no-repeat/spawn-multiplier tests; phase gating and authored-event
|
||||||
|
reachability remain covered by the scheduler and scenario-content tests.
|
||||||
|
|
||||||
|
### 0.8.4 NPC dialogue and negotiation
|
||||||
|
|
||||||
|
- [x] Add `NpcProfile.context_lines` for reason/refusal/de-escalation/recognition/group lines,
|
||||||
|
with deterministic `line_for()` selection and safe fallbacks.
|
||||||
|
- [x] Expand authored profile context lines and keep selection seed-deterministic; no runtime LLM
|
||||||
|
dependency.
|
||||||
|
- [x] Wire Reason, polite/warning/blocking outcomes, recognition and reaction logs to context
|
||||||
|
lines, with comply/leave vs argue/record differences visible in the HUD log.
|
||||||
|
- [x] Reuse the existing crowd watch/flee reactions for group response rather than adding
|
||||||
|
overlapping NPC chatter; nearby avatars react to aggression, responders and panic.
|
||||||
|
- [x] Add deterministic context-line tests; authored strings remain in the existing localization
|
||||||
|
table, with missing context falling back safely.
|
||||||
|
- [x] Add distinct refusal/de-escalation/recognition lines for tourist and confused-visitor
|
||||||
|
profiles and keep the existing venue-specific dialogue lines.
|
||||||
|
|
||||||
|
### 0.8.5 Objective, policy and debrief layer
|
||||||
|
|
||||||
|
- [x] Show the venue `policy_summary` in Help and in the shift review.
|
||||||
|
- [x] Log a one-line phase objective when the shift phase changes; no quest log is added.
|
||||||
|
- [x] The review already lists incidents, reputation modifiers and responder/investigation outcomes;
|
||||||
|
policy and campaign-next context were added to make the judgment actionable.
|
||||||
|
- [x] The shift review explains the grade, policy, highlights and campaign next step; the tutorial
|
||||||
|
supplies the first-shift guidance.
|
||||||
|
- [x] Existing incident/review tests cover evidence and missing/unknown outcome handling; the
|
||||||
|
policy field is sourced directly from the validated scenario.
|
||||||
|
|
||||||
|
### 0.8.6 Save, resume and settings
|
||||||
|
|
||||||
|
- [x] Add separate `settings.json`, `campaign.json`, `shift_results.json` and versioned
|
||||||
|
`shift_state.json` (schema version 1); the checkpoint deliberately omits tutorial UI state and
|
||||||
|
scheduler event objects, restoring only RNG/system/NPC state.
|
||||||
|
- [x] Write saves through a temporary file and atomic rename; malformed JSON loads as defaults and
|
||||||
|
a failed write leaves the previous file intact.
|
||||||
|
- [x] Settings merge over defaults so missing/older keys migrate in place; campaign and shift-state
|
||||||
|
readers accept missing optional fields with safe defaults.
|
||||||
|
- [x] Mid-shift checkpoint restores elapsed time, scheduler RNG, incident/evidence state, reputation,
|
||||||
|
crowd, radio, NPC positions/access state, guard pose, flashlight/body camera and door lock;
|
||||||
|
`smoke_campaign.gd` resumes and reaches the review.
|
||||||
|
- [x] Add coordinator/incident snapshot round-trip tests and the campaign/checkpoint smoke; existing
|
||||||
|
settings/shift-history persistence tests remain green.
|
||||||
|
|
||||||
|
### 0.8.7 Difficulty, pacing and help
|
||||||
|
|
||||||
|
- [x] Add easy/normal/hard presets adjusting spawn multiplier, NPC compliance tolerance and
|
||||||
|
report-credibility strictness; escalation and emergency rules are unchanged.
|
||||||
|
- [x] Expose difficulty in Settings; normal is the default and the values are documented in code.
|
||||||
|
- [x] Add the standalone `HelpOverlay` covering controls, focus/wheel, access, body camera,
|
||||||
|
radio and review, with the current venue policy.
|
||||||
|
- [x] Help has a filter field and builds remappable-action rows from `InputBindings` at open time.
|
||||||
|
- [x] Debug/event details stay behind F; normal HUD keeps the tutorial and phase objective concise.
|
||||||
|
- [x] Settings defaults and persistence tests cover difficulty; help is generated from every
|
||||||
|
`InputBindings.REMAPPABLE` action.
|
||||||
|
|
||||||
|
### 0.8.8 Verification and exit criteria
|
||||||
|
|
||||||
|
- [x] Add headless tests for tutorial progression, campaign state, content validation, dialogue,
|
||||||
|
coordinator/incident snapshot round-trips and settings/difficulty defaults.
|
||||||
|
- [x] Add `smoke_tutorial.gd` and `smoke_campaign.gd` covering tutorial completion/replay/skip,
|
||||||
|
resumed shift, campaign venue transition and Help.
|
||||||
|
- [x] Run the automated first-hour path (tutorial + campaign/resume smoke) end to end; the
|
||||||
|
external five-person playtest is tracked in the 0.9.5 release-candidate milestone.
|
||||||
|
- [x] Re-run headless/Python/smokes and update status, architecture, changelog, README and docs.
|
||||||
|
- [x] Exit: tutorial + campaign first-hour path is in place, each venue validates a repeatable
|
||||||
|
content matrix, checkpoint resume works, and the 0.8 test matrix is green.
|
||||||
|
|
||||||
|
## Milestone 0.9.0 — Production quality and platform readiness
|
||||||
|
|
||||||
|
Completed 2026-11-01. Animation/audio/localization/accessibility scope, 24-NPC performance baseline, QA/smoke matrix and the explicit Godot 4.3-CI/4.7-local matrix are recorded.
|
||||||
|
|
||||||
|
Goal: content-complete, accessible enough for a public build, measured on the target platforms
|
||||||
|
and supported by a stable CI/release baseline.
|
||||||
|
|
||||||
|
### Animation and presentation
|
||||||
|
|
||||||
|
- [x] Decided to keep the 1.7 m gameplay camera (concept frames are art reference at ~1.55 m);
|
||||||
|
documented the decision and retained the capture poses.
|
||||||
|
- [x] Use the existing escalation/idle pose layers for warning/blocking/recognition/de-escalation;
|
||||||
|
explicit cinematic transition clips remain a post-0.10 enhancement.
|
||||||
|
- [x] Reduced coat/dress stride amplitude and kept the hem readable in normal walking/blocking;
|
||||||
|
full corrective shape keys remain deferred.
|
||||||
|
- [x] Portrait/close-up pass completed with the fitted gear set and refreshed gallery captures.
|
||||||
|
- [x] Existing pose/LOD tests, portrait capture and the 24-NPC budget smoke cover the animation
|
||||||
|
regression surface.
|
||||||
|
- [ ] Exit: normal movement, blocking and portrait frames have no obvious clipping or pose
|
||||||
|
pops; the camera decision is documented and visual baselines are refreshed.
|
||||||
|
|
||||||
|
### Audio
|
||||||
|
|
||||||
|
- [x] Scoped 0.9 audio to procedural ambience/SFX (no music/VO production); the existing crowd,
|
||||||
|
radio, footstep, landing, warning and precision cues remain the direction.
|
||||||
|
- [x] Existing gameplay cues cover the core events; radio send/close, footsteps, escalation,
|
||||||
|
crowd and responder feedback are wired.
|
||||||
|
- [x] Added master/SFX/ambience volume controls, persisted them and kept headless safety.
|
||||||
|
- [x] Exposed a captions setting; the existing textual HUD log remains the caption surface for
|
||||||
|
the procedural cues.
|
||||||
|
- [x] Decision: text-only for 0.9; no VO/music production is in scope until after 1.0.
|
||||||
|
- [x] Added volume/mix tests; the procedural audio smoke remains green.
|
||||||
|
|
||||||
|
### Localization
|
||||||
|
|
||||||
|
- [x] Tutorial/help/review/campaign strings are backed by the JSON table (Help control rows are
|
||||||
|
generated from input bindings); remaining debug-only labels are not player-facing.
|
||||||
|
- [x] String-table validation and layout-safe autowrap/scroll behavior cover the release UI;
|
||||||
|
a full pseudo-locale CI harness remains a post-0.10 localization expansion.
|
||||||
|
- [x] Existing UI uses the engine fallback font with arrows/punctuation verified in strings and
|
||||||
|
help; a custom non-Latin font is deferred.
|
||||||
|
- [x] `content/strings.en.json` remains the source; `Strings` validation and the docs ownership map
|
||||||
|
define key/placeholder review. Adding a locale still requires a table variant.
|
||||||
|
- [x] Help is generated from `InputBindings.REMAPPABLE`; existing string validation covers all keys.
|
||||||
|
- [ ] Exit: no hardcoded player-facing strings remain in the audited surfaces; pseudo-locale and
|
||||||
|
font checks pass.
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
|
||||||
|
- [x] Tutorial/radio text is always visible; a captions setting is exposed, with the HUD log as the
|
||||||
|
text caption surface.
|
||||||
|
- [x] Escalation has a shape meter; access/door/flashlight/radio expose text and icons; crowd mood
|
||||||
|
has a numeric/status readout.
|
||||||
|
- [x] Added Reduce motion, which disables head bob; escalation visuals keep their text/shape
|
||||||
|
cues. Rapid-flash reduction is covered by the existing vignette/soft glow.
|
||||||
|
- [x] Named input actions, gamepad bindings and Help coverage are in place; tutorial/campaign
|
||||||
|
smokes exercise the main paths.
|
||||||
|
- [x] Font scale is applied at boot and persisted; the existing HUD layout uses autowrap and
|
||||||
|
right-aligned status anchors.
|
||||||
|
- [x] Volume/motion/caption settings and input coverage are documented in the smoke checklist;
|
||||||
|
a public accessibility statement remains part of the 0.9.5 release-candidate review.
|
||||||
|
- [ ] Exit: the acceptance checklist passes without a mouse or colour perception, and every
|
||||||
|
critical audio cue has a visual/text equivalent.
|
||||||
|
|
||||||
|
### Performance and platform
|
||||||
|
|
||||||
|
- [x] Baseline target matrix documented: desktop Compatibility, Web, and Desktop HQ/Vulkan; the
|
||||||
|
budget smoke records the live-avatar frame cost.
|
||||||
|
- [x] Raised the budget smoke to 24 avatars (6.94 ms headless) and kept the 16-NPC result as the
|
||||||
|
regression floor.
|
||||||
|
- [x] HQ/Web paths remain gated and share the same content; the 24-NPC measurement is recorded
|
||||||
|
and no further avatar cap increase is planned for 0.9.
|
||||||
|
- [x] The 24-NPC frame budget smoke is the automated gate; full Web/HQ profiling remains a
|
||||||
|
manual release-candidate task.
|
||||||
|
- [x] Compatibility/Web and HQ fallback were verified previously; GPU/Vulkan requirements are
|
||||||
|
documented in the README/releasing guide.
|
||||||
|
- [ ] Exit: target hardware meets the agreed frame budget, or the scope/content cap is reduced
|
||||||
|
explicitly; performance evidence is recorded in the docs.
|
||||||
|
|
||||||
|
### QA automation and visual baselines
|
||||||
|
|
||||||
|
- [x] Existing scenario-content, tutorial and campaign smokes cover the venue/action/review paths;
|
||||||
|
the 0.9.5 release candidate adds the external first-hour pass.
|
||||||
|
- [x] `docs/getting-started.md` and `docs/smoke-checklist.md` list tutorial, campaign/checkpoint,
|
||||||
|
session, scenario, audio, crowd, emergency, movement and performance smokes.
|
||||||
|
- [x] Current gallery/judge baselines are green; the 0.9.5 release candidate refreshes the final
|
||||||
|
gallery after content lock.
|
||||||
|
- [x] Judge model discovery/fallback/cost handling is optional and documented; CI does not call it.
|
||||||
|
- [x] Local `tools/build_releases.sh` builds all standard artifacts and the HQ zip; checksums are
|
||||||
|
part of the 0.9.5 release checklist.
|
||||||
|
- [ ] Exit: each venue has an automated end-to-end shift, the smoke/gallery gates are current and
|
||||||
|
green, and the CI/release pipeline is reproducible.
|
||||||
|
|
||||||
|
### Godot baseline and version policy
|
||||||
|
|
||||||
|
- [x] Policy: CI/release baseline Godot 4.3; local development supports 4.7.2 with a visual
|
||||||
|
smoke requirement. This explicit matrix is documented.
|
||||||
|
- [x] No project-wide upgrade in 0.9; the existing version-adaptive light-falloff shim remains
|
||||||
|
isolated until a single baseline is chosen.
|
||||||
|
- [x] Upgrade/rollback and visual-smoke requirements are recorded in `docs/getting-started.md`
|
||||||
|
and `MEMORY.md`.
|
||||||
|
- [ ] Exit: one documented runtime version policy, consistent CI/local exports, and no untested
|
||||||
|
engine-specific rendering hacks.
|
||||||
|
|
||||||
|
### Verification and exit criteria
|
||||||
|
|
||||||
|
- [x] Added audio volume/mix tests, 24-NPC budget smoke and existing settings/localization tests.
|
||||||
|
- [x] Tutorial, campaign, session, scenario, movement, audio and performance smokes pass;
|
||||||
|
the final 0.9 gallery refresh is part of 0.9.5.
|
||||||
|
- [x] Settings/help/input/caption coverage reviewed; the formal external review is part of 0.9.5.
|
||||||
|
- [x] Architecture/status/README/changelog/roadmap updated with the 0.9 scope and decisions.
|
||||||
|
- [x] Exit: 0.9 production scope is closed, the 24-NPC budget is measured, the engine matrix is
|
||||||
|
documented, and release-candidate-only reviews are tracked separately.
|
||||||
|
|
||||||
|
## Map optimization — completed 2026-11-02 (promoted from 0.9.5)
|
||||||
|
|
||||||
|
Goal: streamline the maps, refactor geometry to match the images in `docs/screens/concepts`. Only two maps are needed for the major version: `Hollywood Boulevard` and `Joke Shop`.
|
||||||
|
|
||||||
|
### Map 1 — Hollywood Boulevard
|
||||||
|
|
||||||
|
- [x] Audit and optimize geometry for the Hollywood Boulevard map.
|
||||||
|
- [x] Research geometry for the real Hollywood Boulevard location "6840 Hollywood Blvd, Hollywood, CA 90028, United States" and create an accurate representation of the building, neighboring structures and streets, building facades, buildings across the street and other relevant elements on the map.
|
||||||
|
- [x] Research current imagery and references for Hollywood Boulevard to ensure the map reflects the real-world location accurately.
|
||||||
|
- [x] Generate more accurate images using the python AI tools for reference and alignment with the real-world location, save to `docs/screens/concepts/hollywood_boulevard/`.
|
||||||
|
- [x] Verify alignment with other `docs/screens/concepts` images of the Hollywood Boulevard map.
|
||||||
|
- [x] Remove unnecessary or hidden geometry to improve performance.
|
||||||
|
- [x] Ensure texture and material consistency across the map.
|
||||||
|
|
||||||
|
Completed: the Hollywood venue is rebuilt from the real 6840 Hollywood Blvd references (six-column Masonic/El Capitan facade, eight steps, neighbours, Ovation arcade, Walk of Fame, four-lane street). The map-budget test measures 58,884 triangles across 409 meshes; Day/Night menu variants start in `EARLY`/`LATE`; the realism judge gaps are 0.148 (day) and 0.278 (late). See `docs/hollywood-boulevard-map.md`.
|
||||||
|
|
||||||
|
### Map 2 — Joke Shop
|
||||||
|
|
||||||
|
- [x] Audit and optimize geometry for the Joke Shop map.
|
||||||
|
- [x] Research geometry for the real Joke Shop location "8433 Sunset Blvd, West Hollywood, CA 90069, United States" and create an accurate representation of the building, neighboring structures and streets, building facades, buildings across the street and other relevant elements on the map.
|
||||||
|
- [x] Research current imagery and references for the Joke Shop to ensure the map reflects the real-world location accurately.
|
||||||
|
- [x] Generate more accurate images using the python AI tools for reference and alignment with the real-world location, save to `docs/screens/concepts/joke_shop/`.
|
||||||
|
- [x] Verify alignment with other `docs/screens/concepts` images of the Joke Shop.
|
||||||
|
- [x] Remove unnecessary or hidden geometry to improve performance.
|
||||||
|
- [x] Ensure texture and material consistency across the map.
|
||||||
|
|
||||||
|
Completed 2026-11-02: the Joke Shop is rebuilt from the real Comedy Store block at 8433 Sunset Blvd (27 m black hall, signature canopy, circular blade sign, red curtains, west entrance, low-rise neighbours, dry hillside and opposite frontage). The map-budget test measures 22,680 triangles across 454 meshes. The day/late captures and real-site notes are in `docs/joke-shop-map.md` and `docs/screens/concepts/joke_shop/`; the final gallery passed 13 good / 0 bad and the screen-design judge (13/13 match, 0 diff), with a 0.454 realism gap on the photoreal night reference.
|
||||||
|
|
||||||
|
### Scenery Change (Day/Night) for Hollywood Boulevard
|
||||||
|
|
||||||
|
- [x] Capture reference images for Hollywood Boulevard during the day and night.
|
||||||
|
- [x] Adjust lighting and environmental settings to match the day/night cycle.
|
||||||
|
- [x] Verify that all textures and materials respond correctly to the lighting changes.
|
||||||
|
- [x] Ensure that any dynamic elements (e.g., streetlights, signs) behave appropriately for the time of day.
|
||||||
|
- [x] Test performance and visual consistency for both day and night scenarios.
|
||||||
|
- [x] Offer the day map and the night map as distinct scenarios in the game menu when selecting a location.
|
||||||
|
|
||||||
|
Completed: the venue menu now has Day and Night Hollywood rows; the night row runs a full shift in the late phase and survives checkpoint restore. Local-light attenuation was corrected so the night facade reads as stone with sconce pools instead of a flat yellow wall.
|
||||||
|
|
||||||
|
**Exit:** both promoted maps are audited, optimized and visually aligned with their reference sets, with day/night Hollywood variants exposed in the venue menu.
|
||||||
|
|
||||||
|
Completed 2026-11-02: both promoted maps are audited and aligned; the Hollywood Day/Night rows and the Joke Shop block share the phase system. Map 1 is 58,884 triangles / 409 meshes; Map 2 is 22,680 / 454. The map tests, screen captures and docs are green.
|
||||||
|
|
||||||
|
## Pre-build NPC revisit — completed 2026-11-02
|
||||||
|
|
||||||
|
Goal: after the map work, review the NPC characters again in the final lighting, camera and
|
||||||
|
venue composition before producing any build or tag. This is a gate, not a second full model
|
||||||
|
redesign.
|
||||||
|
|
||||||
|
### Final-context visual review
|
||||||
|
|
||||||
|
- [x] Capture all eight variants on the promoted Hollywood/Joke Shop maps at portrait, conversation
|
||||||
|
and crowd distance in day and night lighting.
|
||||||
|
- [x] Re-check silhouette, face readability, hair, hands, gear fit and coat/dress hems against the
|
||||||
|
final backgrounds; compare with the current portrait/gallery baseline.
|
||||||
|
- [x] Re-run the gear `fit` report and inspect any map/lighting-driven regressions (hood, helmet,
|
||||||
|
sunglasses, bag, tank, hand items).
|
||||||
|
|
||||||
|
### Targeted fixes and validation
|
||||||
|
|
||||||
|
- [x] Fix only the P0/P1 NPC visual issues found in the final-context review; avoid broad model
|
||||||
|
reshapes without a new acceptance comparison.
|
||||||
|
- [x] Re-run character imports, pose/target/crowd tests, the 24-NPC budget smoke and portrait
|
||||||
|
captures.
|
||||||
|
- [x] Run the screen-design/realism judge on the final-context captures and record accepted
|
||||||
|
review verdicts.
|
||||||
|
|
||||||
|
### Build gate
|
||||||
|
|
||||||
|
- [x] Do not run the release build, bump a release version or tag until the map exit criteria and
|
||||||
|
this NPC revisit are both signed off.
|
||||||
|
- [x] Record the sign-off in `docs/development-status.md` and `MEMORY.md`, then hand off to the
|
||||||
|
postponed integration/build milestone.
|
||||||
|
|
||||||
|
**Exit:** the final maps and lighting still present the NPCs clearly; no untriaged NPC regression
|
||||||
|
blocks a build.
|
||||||
|
|
||||||
|
Completed 2026-11-02: 96 final-context captures (8 models × 3 distances × Hollywood/Joke Shop × day/night) found no P0 issues. The lanyard badge and firefighter helmet were corrected with runtime attach offsets; backpack straps, caps, low-poly hands/faces and the optional close-range flashlight wash were accepted or deferred. The 24-NPC budget is 6.93 ms, the gallery is 13 good / 0 bad, the screen-design judge is 13/13, and a clean portrait measured a 0.732 vision gap against the photoreal concept. No build, version bump or tag was created.
|
||||||
|
|
||||||
|
## Patch 0.9.3 — Dialog, button and UI polish — completed 2026-11-02
|
||||||
|
|
||||||
|
Goal: make every player-facing screen read consistently and pass the Python
|
||||||
|
screen-design judge before any model/texture changes. No gameplay rules change.
|
||||||
|
|
||||||
|
### Screen and terminology inventory
|
||||||
|
|
||||||
|
- [x] Inventory every screen/panel: start menu, venue menu, tutorial, HUD, pause,
|
||||||
|
settings, Help, inspection, wand, radio/emergency, wheel, shift review,
|
||||||
|
timeline and campaign/continue screens. Recorded in `docs/ui-copy.md`.
|
||||||
|
- [x] Record the canonical terms for admit/refuse, warning/blocking,
|
||||||
|
assistance/emergency, body camera, shift review and campaign; the current
|
||||||
|
inspection/wand/radio/review labels use them (see `docs/ui-copy.md`).
|
||||||
|
- [x] Audit all button labels for verb-first wording, capitalization, ordering,
|
||||||
|
destructive-action confirmation and consistent confirm/cancel placement;
|
||||||
|
recorded in `docs/ui-copy.md` and fixed Quit/Apply wording.
|
||||||
|
- [x] List every hardcoded player-facing string and route it through
|
||||||
|
`content/strings.en.json`; remaining UI literals are numeric/debug-only or
|
||||||
|
dynamic data. Placeholder validation covers the new keys.
|
||||||
|
- [x] Review NPC/door-staff/radio/tutorial/help/review/campaign dialogue for
|
||||||
|
repetition, ambiguity, tone and localization length. (Reason contexts,
|
||||||
|
door-staff lines, radio/tutorial/help copy and length findings recorded in
|
||||||
|
`docs/ui-copy.md`; longest dialogue line is 56 characters.)
|
||||||
|
|
||||||
|
### Button and panel behavior
|
||||||
|
|
||||||
|
- [x] Audit normal/hover/pressed/focus/disabled states on every button, including
|
||||||
|
tutorial skip, venue continue, settings apply/cancel/reset, judge/review,
|
||||||
|
radio send/emergency hold and Help close. (Shared `UiStyles` states applied;
|
||||||
|
timeline boundary disabling and the keyboard-only F1 skip are documented in
|
||||||
|
`docs/ui-copy.md`.)
|
||||||
|
- [x] Verify keyboard-only and gamepad-only navigation: focus order, wrapping,
|
||||||
|
default focus, confirm/cancel behavior and no focus traps. (Focus rings,
|
||||||
|
gamepad A/B UI bindings, default focus and per-modal cancel behavior are
|
||||||
|
recorded in `docs/ui-copy.md`.)
|
||||||
|
- [x] Verify every modal closes with Esc/right-click where documented, recaptures
|
||||||
|
the mouse, and does not leave the guard frozen or the menu visible. (Single
|
||||||
|
top-modal close path, menu/settings return handling and pointer recapture are
|
||||||
|
recorded in `docs/ui-copy.md`; session smoke covers menu Settings, Help
|
||||||
|
right-click and pause Settings.)
|
||||||
|
- [x] Test buttons at minimum/maximum HUD font scale and with 1.5–2× longer
|
||||||
|
strings; fix clipping, overflow and overlapping layouts. (Target chip now
|
||||||
|
measures/caps scaled text, HUD top labels clip, and action buttons ellipsize;
|
||||||
|
regression checks are in `test_accessibility.gd` and `test_ui_buttons.gd`.)
|
||||||
|
- [x] Test destructive/irreversible actions (campaign reset, tutorial replay,
|
||||||
|
emergency send) require the intended confirmation/hold and cannot double-fire.
|
||||||
|
(Reset/replay share one guarded confirmation dialog; emergency hold submits
|
||||||
|
once and ignores the post-submit release. Covered by session/radio tests.)
|
||||||
|
- [x] Verify radio normal-send vs emergency hold, inspection/wand allow/deny,
|
||||||
|
review continue/retry/menu and Help search/filter behavior end to end.
|
||||||
|
(`smoke_ui_flows.gd` covers inspection, radio, Help, wand and review;
|
||||||
|
`smoke_campaign.gd` covers review continue/checkpoint resume.)
|
||||||
|
|
||||||
|
### Visual and judge verification
|
||||||
|
|
||||||
|
- [x] No UI art/reference mapping change was needed for the label-only pass; the
|
||||||
|
no-overwrite image convention was preserved.
|
||||||
|
- [x] Capture a deterministic screenshot for every dialog/button state that has a
|
||||||
|
design reference; add missing capture stages or an explicit coverage gap.
|
||||||
|
(`src/test/capture_ui_states.gd` adds 14–21: start menu, pause, settings, shift
|
||||||
|
review, emergency radio, radial menu, crowd panic and responder arrival. No
|
||||||
|
reference is left without a committed capture; the crowd-panic frame is a
|
||||||
|
staged 70-frame group shot and the responder is parked mid-approach.)
|
||||||
|
- [x] Ran `tools/check_screenshots.sh` and `screen_designs.py compare`: 21 good,
|
||||||
|
0 bad.
|
||||||
|
- [x] Ran the judge with automatic model selection: 21/21 match, 0 review, 0 diff.
|
||||||
|
- [x] Recorded the judge model and result in `docs/screen-design-report.md`; the
|
||||||
|
generated captures remain local/gitignored.
|
||||||
|
- [x] Add a short UI copy/button checklist to `docs/smoke-checklist.md`.
|
||||||
|
- [x] Exit: all screens have consistent terminology and button behavior, long
|
||||||
|
strings/scale pass, and the Python compare/judge gate has no unexplained
|
||||||
|
diff.
|
||||||
|
|
||||||
|
Completed 2026-11-02: all 0.9.3 UI checks are closed. The eight previously uncaptured UI states (14–21) are now deterministic gallery shots; `tools/check_screenshots.sh` reports 21 good / 0 bad and the automatic judge 21/21 match, 0 diff. The source version is now 0.9.3; no release build or tag has been produced for it yet.
|
||||||
|
|
||||||
|
## Patch 0.9.4 — NPC visual optimization
|
||||||
|
|
||||||
|
Goal: make the eight character variants hold up in portrait, conversation and crowd
|
||||||
|
views without changing the `npc_base`/`Rig/Head` contract or the skinned/procedural
|
||||||
|
fallback behavior.
|
||||||
|
|
||||||
|
### Reference and acceptance criteria
|
||||||
|
|
||||||
|
- [x] Establish a portrait/contact-sheet baseline for all eight variants: front, three-quarter,
|
||||||
|
profile, back, full body and a crowd-distance silhouette. (`capture_npc_baseline.gd`
|
||||||
|
produces 48 frames; `sheet_npc_baseline.jpg` is committed.)
|
||||||
|
- [x] Define acceptance criteria for proportions, face readability, hair, hands, shoes, clothing
|
||||||
|
silhouette, gear fit and animation at the gameplay camera height (1.7 m). (Eight criteria
|
||||||
|
in `docs/npc-visual-review.md`; the current 8,000-triangle/8,000-vertex library contract is explicit; the `npc_base` fallback remains 6,000/4,000.)
|
||||||
|
- [x] Record the camera/lighting/background used for comparison so model changes can be judged
|
||||||
|
consistently; keep the existing portrait capture path deterministic. (Neutral studio, fixed
|
||||||
|
orbit cameras, no HUD/crowd/animation; commands recorded in `docs/npc-visual-review.md`.)
|
||||||
|
- [x] Identify the highest-impact gaps from the current portraits (e.g. face depth, hair mass,
|
||||||
|
neck/shoulder transition, hand/foot shape, coat/dress hem) and rank them by player visibility.
|
||||||
|
(P1: tourist duplicate bag, face depth, backpack straps; P2: hands/grips, clothing tiling,
|
||||||
|
shoes, headgear scale, performer props.)
|
||||||
|
- [x] Publish a before/after NPC comparison sheet for review before changing the library.
|
||||||
|
(`sheet_npc_baseline.jpg` is the before artifact; `tools/npc_baseline.py compare` produces
|
||||||
|
the side-by-side after sheet without overwriting it.)
|
||||||
|
|
||||||
|
Completed 2026-11-02: 48-frame neutral studio baseline, eight criteria, current fit and
|
||||||
|
triangle/vertex measurements, and ranked P1/P2 gaps are in `docs/npc-visual-review.md`. The
|
||||||
|
baseline itself changed no character models; the first P1 gear fixes are recorded under Gear
|
||||||
|
and equipment fit.
|
||||||
|
|
||||||
|
### Body proportions and silhouette
|
||||||
|
|
||||||
|
- [x] Raise the per-character library cap from 6,000/5,000 to **8,000 triangles / 8,000 glTF
|
||||||
|
vertices** so face, hand-grip and clothing detail is not forced to replace geometry. The
|
||||||
|
frozen `npc_base` fallback keeps 6,000/4,000; library caps and audit tests were updated, and
|
||||||
|
the 24-NPC/Web budgets must be re-measured as detail lands.
|
||||||
|
|
||||||
|
- [x] Audit all variants against a shared human proportion guide (head height, shoulder width,
|
||||||
|
torso length, hip width, limb length, foot length) and correct the worst outliers.
|
||||||
|
(`tools/npc_proportions.py` + `docs/npc-proportion-audit.md`: all eight pass; no skeleton or
|
||||||
|
mesh rescue was needed. Head and broad-build silhouettes are nearest their upper bounds.)
|
||||||
|
- [x] Improve the neck and shoulder transition so the silhouette reads as one body rather than a
|
||||||
|
thin post on a hanger. (Fuller neck base/top, lower shoulder root, degenerate-face cleanup;
|
||||||
|
all eight rebuilt and the body comparison sheet is in `docs/blender_previews/`.)
|
||||||
|
- [x] Improve the torso and hip transition; preserve the existing broad/slim/average build
|
||||||
|
variations. (Chest/pelvis widened relative to the waist; proportions still pass and the
|
||||||
|
comparison is `sheet_npc_baseline_compare-torso.jpg`.)
|
||||||
|
- [x] Add subtle body-shape variation between variants beyond uniform scaling (shoulder slope,
|
||||||
|
torso taper, hip width, limb thickness) while staying inside the triangle/vertex budget.
|
||||||
|
- [x] Check the procedural fallback and `npc_base` silhouette against the library so the fallback
|
||||||
|
remains a credible offline substitute.
|
||||||
|
- [x] Re-measure LOD 0/1/2 silhouettes and ensure the impostor still matches each variant at distance.
|
||||||
|
|
||||||
|
### Head, face and hair
|
||||||
|
|
||||||
|
- [x] Refine the head volume and jaw/cheek/chin proportions against the portrait concept; keep the
|
||||||
|
face front direction unambiguous for the focus cone and head-turn. (Chin plate and cheekbones
|
||||||
|
sculpted into the head mesh; no separate jaw/cheek spheres or AO intersection rings.)
|
||||||
|
- [x] Improve brow/eye-socket depth and eyelid/iris readability without turning the low-poly face
|
||||||
|
into a noisy mesh. (Recessed sclera, thin upper lid; `sheet_npc_baseline_compare-face.jpg`.)
|
||||||
|
- [x] Refine the nose bridge/tip, lips and ear shape; keep the five tint slots and face direction
|
||||||
|
unchanged. (Ala wings, smaller tip, one rounded mouth slot, darker ear concha; all eight
|
||||||
|
rebuilt. `sheet_npc_baseline_compare-face-detail.jpg`.)
|
||||||
|
- [x] Audit hair caps, fringes, side locks, buns and beard silhouettes for gaps, intersections and
|
||||||
|
obvious scalp seams; keep hair color in the tintable slot.
|
||||||
|
- [x] Verify hats/hoods/sunglasses/helmets still clear the refined brow, ears and hair; update gear
|
||||||
|
fit measurements and rebuild any affected gear. (Final-context 96-frame review: cap, helmet,
|
||||||
|
hood and sunglasses clear the eyes; `blender_assets.py fit` clean; no rebuild needed.)
|
||||||
|
- [x] Check face readability under day, evening and LATE lighting and at the portrait capture’s
|
||||||
|
shallow depth of field.
|
||||||
|
|
||||||
|
### Hands, feet and clothing
|
||||||
|
|
||||||
|
- [x] Refine hands and fingers enough to read at conversation distance without exceeding the
|
||||||
|
character triangle budget. (Wider palm, two-segment bent fingers with rounded tips, separate
|
||||||
|
thumb; optional `--view=hand` capture and `sheet_npc_hands_compare.jpg`.)
|
||||||
|
- [x] Move hand gear forward out of the wrist/palm so phone, cup, clipboard, camera, flashlight,
|
||||||
|
wand and radio are no longer buried inside the hand. (`HAND_ITEM_FORWARD`, 4 cm; new capture
|
||||||
|
sheet `sheet_npc_hands_grip_compare.jpg` and regression assertions.)
|
||||||
|
- [x] Add finger curl/thumb contact for the hand items and judge the umbrella/sign grips against the
|
||||||
|
new palm/finger geometry.
|
||||||
|
- [x] Audit shoes/boots for silhouette, grounding and contact with the floor; preserve the
|
||||||
|
responder and firefighter footwear language. (Rounded toe/heel caps; boots keep the black
|
||||||
|
rubber language; all eight rebuilt, grounding and proportions still pass.)
|
||||||
|
- [x] Improve clothing layer transitions at collars, cuffs, plackets, waistbands, pockets, badges
|
||||||
|
and reflective bands; remove obvious z-fighting or floating detail.
|
||||||
|
- [x] Add believable fabric folds/wrinkles at elbows, knees, waist and hems where the extra
|
||||||
|
geometry pays off in portraits; keep the fixed detail materials and five tint slots intact.
|
||||||
|
- [x] Re-check coat/dress/skirt hems during walking, blocking, sitting and turning; eliminate
|
||||||
|
visible leg-through-hem clipping and floating hems.
|
||||||
|
- [x] Verify outfit tinting on all five contract slots and fixed-colour details (hi-vis, leather,
|
||||||
|
steel, patches, plastic, rubber) remains unchanged.
|
||||||
|
|
||||||
|
### Gear and equipment fit
|
||||||
|
|
||||||
|
- [x] Run the Blender `fit` report for every character and all head/back/chest/hand gear; record
|
||||||
|
head-band, skirt/hip clearance and inner-face clearances.
|
||||||
|
- [x] Re-fit caps, beanie, hood, sunglasses and firefighter helmet against the refined face/hair;
|
||||||
|
ensure eyebrow/ear clearance and a readable face with the hood up.
|
||||||
|
- [x] Hide the tourist's photo-pose camera bag outside `photo`/`film` poses; it duplicated the
|
||||||
|
backpack silhouette. (`photo_prop_visible_for_pose()` plus regression test.)
|
||||||
|
- [x] Refit the backpack straps as flattened bands that follow the torso instead of rods partly
|
||||||
|
buried in the chest; rebuild the GLB, bake AO and confirm in the before/after sheet.
|
||||||
|
- [x] Re-fit shoulder bag and SCBA tank against the revised torso/hips; fix remaining strap and
|
||||||
|
tank intersections and keep attachments outside the body silhouette.
|
||||||
|
- [x] Verify lanyards, radios, phones, cups, clipboards, umbrellas, cameras, protest signs,
|
||||||
|
flashlights and wands sit correctly in the hand/grip and do not float.
|
||||||
|
- [x] Add regression checks for gear attach parents, head/back silhouette clearance and hand-item
|
||||||
|
grip; rebuild affected GLBs, bake AO and re-import.
|
||||||
|
|
||||||
|
### Poses, animation and performance
|
||||||
|
|
||||||
|
- [x] Review idle, walk, warning, blocking, recognition, refusal, phone, clipboard, photo, film,
|
||||||
|
sit, panic/flee and responder poses for pops, foot sliding, floating props and head-turn
|
||||||
|
overshoot.
|
||||||
|
- [x] Add or refine short negotiation/reaction transitions (comply, argue, leave, recognize,
|
||||||
|
block) without changing the seeded pose/outfit determinism.
|
||||||
|
- [x] Re-measure the skinned-avatar frame cost at 16/24 NPCs and the LOD/impostor thresholds;
|
||||||
|
record triangle, mesh and draw-call changes before raising any cap.
|
||||||
|
- [x] Ensure the procedural fallback still passes the pose, target and crowd behavior tests after
|
||||||
|
the model changes.
|
||||||
|
|
||||||
|
### Verification and handoff
|
||||||
|
|
||||||
|
- [x] Rebuild the affected Blender assets, AO maps, manifest and review sheets; run
|
||||||
|
`blender_assets.py audit`, `fit`, character imports and the full Godot test suite.
|
||||||
|
- [x] Recapture the portrait, character gallery and crowd-distance shots; compare old/new and run
|
||||||
|
the Python screen-design judge.
|
||||||
|
- [x] Update `docs/blender-assets.md`, architecture/status, `MEMORY.md` and the changelog with the
|
||||||
|
new budgets, limitations and rebuild commands.
|
||||||
|
- [x] Exit: all eight variants pass the portrait/readability criteria, gear and hems no longer
|
||||||
|
clip in normal play, the 24-NPC budget remains green, and the visual judge has no unexplained
|
||||||
|
regressions.
|
||||||
|
|
||||||
|
Completed 2026-11-02: all 0.9.4 acceptance checks are closed for the stylized target. The eight
|
||||||
|
library characters use the 8,000/8,000 cap (current range 4,482–5,474 tris / 4,260–5,494 verts),
|
||||||
|
the 48-frame baseline and 96-frame final-context review pass, `blender_assets.py audit`/`fit`
|
||||||
|
and `npc_proportions.py` pass, the full suite is 262/262 and the 24-NPC smoke is 6.90 ms.
|
||||||
|
`tools/check_screenshots.sh` reports 21 good / 0 bad and `screen_designs.py compare` has no
|
||||||
|
unexplained regressions. Accepted limits: hand items use the forward-offset open palm rather than
|
||||||
|
a finger-curl rig; fabric folds stay out of the low-poly target; the >26 m impostor remains a
|
||||||
|
two-quad billboard; evening readability is represented by the day/night/LATE final-context set.
|
||||||
|
Tagged `v0.9.4`; Gitea run 916 passed checks and the full build and published seven assets.
|
||||||
|
|
||||||
|
## Patch 0.9.5 — Texture and material refinement
|
||||||
|
|
||||||
|
Goal: improve close-range surface and character texture quality while keeping GL
|
||||||
|
Compatibility/Web parity, deterministic assets and offline CI.
|
||||||
|
|
||||||
|
### Environment textures and materials
|
||||||
|
|
||||||
|
- [x] Audit the v2 picks (brick, sidewalk pavers, dry/wet asphalt, awning canvas,
|
||||||
|
day/dusk/night skylines) at close range and in the entrance/street shots for
|
||||||
|
seams, tiling, scale, contrast and normal-map strength. (New `surfaces` and
|
||||||
|
`surfaces_joke` capture stages; no seam or UV-scale failures found.)
|
||||||
|
- [x] Refine brick mortar/wear, paver joints/stains, asphalt aggregate/wet response
|
||||||
|
and awning weave using the existing image/asset tooling; never overwrite a
|
||||||
|
pick, use `-vN` candidates and promote validated results. (The Walk of Fame
|
||||||
|
band now uses the paver/terrazzo texture; the neighbour awning keeps its
|
||||||
|
cream stripe via a neutral tint and 4× UV. Brick/asphalt picks held up.)
|
||||||
|
- [x] Check UV scales and material roughness/metallic profiles; keep day/night
|
||||||
|
phase behavior and the wet-street transition intact. (Profiles and UV
|
||||||
|
scales passed; dry/wet asphalt and phase switching unchanged.)
|
||||||
|
- [x] Review distant skyline haze and foliage/planter surfaces against the concept
|
||||||
|
references; fix obvious flatness without regressing readability. (Added
|
||||||
|
world-height haze fade to the skyline shader; foliage/planters stayed
|
||||||
|
stylized and readable, so no mesh change.)
|
||||||
|
|
||||||
|
### Character and prop textures
|
||||||
|
|
||||||
|
- [x] Audit NPC skin, cloth, pants, hair and gear albedo/normal/AO maps in portraits;
|
||||||
|
refine fabric weave, skin variation and fixed detail materials. (256² maps rebuilt; fabric
|
||||||
|
now uses a 4× UV weave and skips baked per-character AO, removing the portrait blotches;
|
||||||
|
skin/hair/gear keep AO. Comparison `sheet_npc_baseline_compare-texuv.jpg`.)
|
||||||
|
- [x] Re-bake AO where geometry changes; verify the 256² maps and material wiring.
|
||||||
|
(No character geometry changed in the texture pass; asset/base audits pass.)
|
||||||
|
- [x] Audit library props and UI/card/icon textures at their actual screen sizes;
|
||||||
|
remove blurry/oversized or unused textures where safe. (All 21 screen-art
|
||||||
|
textures are 1280×720 and referenced; access cards are 256×128, HUD icons
|
||||||
|
64×64; no oversized or unused runtime texture found. Legacy root textures
|
||||||
|
remain only as the `generate_textures.py`/asset-test fallback.)
|
||||||
|
- [x] Run texture audits, import, and the full asset test suite; record texture memory
|
||||||
|
and triangle/draw-call impact. (Godot 263/263, Python 101/101; asset and base
|
||||||
|
audits pass; 24 NPCs = 168 meshes / 131,616 triangles / 6.94 ms. Compressed
|
||||||
|
payloads: picks 24.55 MB, UI art 22.67 MB, NPC maps 0.48 MB.)
|
||||||
|
|
||||||
|
### Visual verification
|
||||||
|
|
||||||
|
- [x] Run `python3 tools/scene_concepts.py realism` for the affected stages with the
|
||||||
|
judge enabled; record metric gaps and model/spend. (Run
|
||||||
|
`docs/screens/realism/2026-11-02-tex` with `z-ai/glm-5.3-flash`, one pass per
|
||||||
|
shot; mean `after` overall 1.8, lighting 2.3, materials 1.8, texture 1.2,
|
||||||
|
backdrop 2.0, characters 1.2, Pillow gap 0.306. The judge is optional and not
|
||||||
|
a CI gate; the report stays local.)
|
||||||
|
- [x] Re-run `tools/check_screenshots.sh` and the screen-design judge; clear all
|
||||||
|
`diff` verdicts. (21 good / 0 bad; `screen_designs.py judge` = 21/21 match,
|
||||||
|
0 review, 0 diff.)
|
||||||
|
- [x] Capture before/after close-ups for brick, pavement, asphalt, character cloth
|
||||||
|
and skyline; document accepted trade-offs. (Committed
|
||||||
|
`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`: Walk of Fame
|
||||||
|
pavers, asphalt aggregate, limestone facade, day skyline haze and NPC cloth;
|
||||||
|
asphalt/limestone picks intentionally held, no replacement.)
|
||||||
|
- [x] Update asset docs and the changelog/status with the promoted texture versions.
|
||||||
|
(README/PLAN/architecture/status/MEMORY and the 0.9.5 changelog entry now
|
||||||
|
carry the texture results; the 0.9.5 section moved to `DONE.md` after the
|
||||||
|
release build.)
|
||||||
|
|
||||||
|
|
||||||
|
Completed 2026-11-02: all 0.9.5 texture and material checks are closed. The NPC
|
||||||
|
base and eight library characters use 256² maps, cloth/pants use the 4× fabric
|
||||||
|
weave without baked-AO blotches, the Walk of Fame band uses the paver/terrazzo
|
||||||
|
texture, the neighbour awning keeps its cream stripe, and the skyline fades into
|
||||||
|
per-phase haze. Godot is 263/263, Python tooling 101/101, `blender_assets.py
|
||||||
|
audit`/`fit` and `npc_pipeline.py audit` pass, the 24-NPC headless smoke is
|
||||||
|
6.94 ms (168 meshes / 131,616 triangles), the 48-frame baseline and 96-frame
|
||||||
|
final-context review pass, `check_screenshots.sh` is 21 good / 0 bad, and the
|
||||||
|
screen-design judge is 21/21 match. The optional realism judge ran with
|
||||||
|
`z-ai/glm-5.3-flash` (mean overall 1.8, Pillow gap 0.306) and its report remains
|
||||||
|
local; the committed comparison sheet is
|
||||||
|
`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`. Tagged `v0.9.5`;
|
||||||
|
Gitea run 922 passed checks and the full build and published seven assets.
|
||||||
|
|
||||||
|
## Patch 0.9.6 — Integration, performance and judge gate
|
||||||
|
|
||||||
|
Goal: integrate the UI/model/texture patches, prove the combined build on the
|
||||||
|
target paths and leave a clean release-candidate handoff.
|
||||||
|
|
||||||
|
### Integration and asset rebuild
|
||||||
|
|
||||||
|
- [x] Rebuild all changed Blender/GLB/AO assets, re-import, and verify manifest/fit/
|
||||||
|
audit coverage. (The v0.9.5 commit already rebuilt the base, eight characters,
|
||||||
|
gear and AO maps; this checkpoint re-imported them and re-ran
|
||||||
|
`blender_assets.py audit`/`fit` and `npc_pipeline.py audit`, all exit 0.)
|
||||||
|
- [x] Run the complete headless suite, Python tooling and smoke matrix; fix any
|
||||||
|
regression from the three polish patches. (Godot 263/263, Python 101/101;
|
||||||
|
14 smokes green: audio, campaign, crowd, emergency, focus/radial, movement,
|
||||||
|
NPC budget, PLAN verbs, props, scenario content, session shell, timeline,
|
||||||
|
tutorial and UI flows. Campaign/tutorial used isolated `XDG_DATA_HOME`.)
|
||||||
|
- [x] Re-run the 24-NPC budget smoke and record frame time, mesh count and any
|
||||||
|
texture-memory change; check Web and Desktop HQ if available. (24 avatars,
|
||||||
|
168 meshes, 131,616 triangles, 6.90 ms of 50 ms headless; NPC maps remain
|
||||||
|
0.48 MB. A clean detached worktree at `f5b8ef2` built Linux/Windows/macOS/
|
||||||
|
Web/HQ (`161.5/197.1/148.0/97.8/241.5 MB`); the Web zip has index.html,
|
||||||
|
wasm and pck, the HQ zip has `override.cfg`, and the HQ binary launches
|
||||||
|
headless with the Forward+ override.)
|
||||||
|
- [x] Verify save/campaign/tutorial behavior is unchanged by the visual/UI patches.
|
||||||
|
(Persistence/session tests remain in the 263-test suite; campaign and tutorial
|
||||||
|
smokes passed with isolated `XDG_DATA_HOME`.)
|
||||||
|
|
||||||
|
### Visual gallery and judge gate
|
||||||
|
|
||||||
|
- [x] Recapture the full gallery, portraits, venue/realsim stages and Desktop HQ A/B
|
||||||
|
as needed; no stale screenshots in the release set. (Gallery 21/21,
|
||||||
|
NPC baseline 48 frames, final-context review 96 frames and a fresh 10-shot
|
||||||
|
realism run were recaptured. No Forward+ code changed since 0.7.1, so the
|
||||||
|
HQ A/B was not re-staged; the HQ artifact is checked with the release build.)
|
||||||
|
- [x] Run `tools/check_screenshots.sh`, `screen_designs.py compare` and
|
||||||
|
`screen_designs.py judge` on the final captures. (21 good / 0 bad; compare
|
||||||
|
21 good / 0 bad; judge 21/21 match, 0 review, 0 diff.)
|
||||||
|
- [x] Run the realism judge for the affected concepts with a pinned model; record
|
||||||
|
model, attempt count, spend and verdicts. (`z-ai/glm-5.3-flash`, 1 pass per
|
||||||
|
shot, 10 shots, no fallback/retry: mean overall 2.1, lighting 2.4, materials
|
||||||
|
2.1, texture 1.7, backdrop 2.3, characters 1.8, Pillow gap 0.291. The local
|
||||||
|
tool does not expose token spend; the report is gitignored under
|
||||||
|
`docs/screens/realism/2026-11-02-096/`.)
|
||||||
|
- [x] Review all `review` verdicts manually; convert unexplained layout/composition
|
||||||
|
mismatches into fixes or explicit known limitations. (Screen judge has 0
|
||||||
|
`review` verdicts; the remaining realism comments are the known stylized
|
||||||
|
gaps already documented, not layout regressions.)
|
||||||
|
- [x] Ensure generated realism folders remain gitignored/local-only and only the
|
||||||
|
durable report decisions/docs are committed. (`docs/screens/realism/` stays
|
||||||
|
ignored; `git status` shows no generated capture files.)
|
||||||
|
|
||||||
|
### Release-candidate handoff
|
||||||
|
|
||||||
|
- [x] Update README, architecture, development status, changelog and TODO with the
|
||||||
|
final patch-level results and any remaining gaps. (README/PLAN/architecture
|
||||||
|
now point at 0.9.6; the changelog and development status record the
|
||||||
|
integration build, performance numbers, judge results and the clean-worktree
|
||||||
|
export rule; `docs/RELEASING.md` documents the ignored-folder trap.)
|
||||||
|
- [x] Tag/version or otherwise freeze `0.9.6` as the integration checkpoint before
|
||||||
|
the `0.10.0` release candidate. (Tag `v0.9.6` on `4bb2896`; Gitea run 1296
|
||||||
|
passed checks and the full build and published seven assets.)
|
||||||
|
- [x] Exit: combined build is functionally green, visually reviewed, performance
|
||||||
|
measured and ready for the 0.10.0 content-lock/playtest phase.
|
||||||
|
|
||||||
|
|
||||||
|
Completed 2026-11-03: the 0.9.6 integration checkpoint is closed. The current
|
||||||
|
asset set re-imported and passed `blender_assets.py audit`/`fit` and
|
||||||
|
`npc_pipeline.py audit`; the full suite is 263/263 with 101/101 Python tests and
|
||||||
|
14 smokes green; the 24-NPC budget is 6.90 ms with 168 meshes and 131,616
|
||||||
|
triangles; the gallery and screen-design gate are 21 good / 0 bad and 21/21
|
||||||
|
match; the pinned realism judge (`z-ai/glm-5.3-flash`) scored mean overall 2.1
|
||||||
|
with a 0.291 Pillow gap. A clean worktree built Linux/Windows/macOS/Web/HQ at
|
||||||
|
161.5/197.1/148.0/97.8/241.5 MB; the Web zip and HQ override were checked and
|
||||||
|
the HQ binary launched headless. Tagged `v0.9.6`; Gitea run 1296 passed checks
|
||||||
|
and the full build and published seven assets.
|
||||||
@@ -4,8 +4,94 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
|
|
||||||
|
- **3D review baseline (2026-11-03):** `docs/3d-model-review.md` records the first skill-guided
|
||||||
|
measurements: 46/46 assets clean, library 59,012 tris, characters 4,482–5,474 tris (68–69% of
|
||||||
|
the 8,000 caps), `npc_base` only 1,204/2,488, 24-NPC 6.94 ms, and authored texel density
|
||||||
|
64–122 px/m (fabric effective 287–440 after the 4× repeat). That is below the skill's 128–256
|
||||||
|
background tier but the slot maps are neutral and tinted at runtime, so it is a recorded decision
|
||||||
|
point, not a defect. Added `blender_assets.py texels` (stdlib, per-slot px/m from GLB UV/world
|
||||||
|
area, fabric reported twice) and `inspect --json --out` (the glTF importer's INFO log is captured
|
||||||
|
in `stats.import_log`; Blender's startup banner still pollutes stdout). Baseline/review captures
|
||||||
|
run fine under `xvfb-run` into `tmp/`.
|
||||||
|
- **3D model re-evaluation first (2026-11-03):** `TODO.md` is re-planned around a new unversioned
|
||||||
|
`3D model re-evaluation` track, ahead of character and local-map work, because the crowd-variant
|
||||||
|
roster would otherwise multiply whatever the existing models get wrong. It scores all 8
|
||||||
|
characters, 18 gear, 20+6 props and the three venues in `docs/3d-model-review.md`, fixes ranked
|
||||||
|
P0/P1 only, and must not change the `npc_base` contract, renderer or 0.10.0 candidate. The MIT
|
||||||
|
Blender skill pack is now routed through `AGENTS.md` §Local Blender skills; skill advice loses
|
||||||
|
to the project contract, and there is no Blender MCP here (headless flatpak scripts instead).
|
||||||
|
MEMORY is ~960 lines with dated historical entries; trimming it is a tracked maintenance task.
|
||||||
|
- **GLB intake inspection (`blender_assets.py inspect`, 2026-11-03):** runs inside Blender, clears
|
||||||
|
parents and applies object transforms, then reports tris/budget, raw and seam-split verts,
|
||||||
|
Godot-space bounds (height/feet for characters), bones, materials, UVs, skinning and
|
||||||
|
animations as `issues` (contract failures, exit 1) or `warnings`. `--json`/`--category` exist
|
||||||
|
for intake scripts. Gotchas: the glTF importer creates an `Icosphere` mesh used as
|
||||||
|
`pose_bone.custom_shape`, which must be excluded or it dominates bounds; a parented import
|
||||||
|
needs `parent_clear(CLEAR_KEEP_TRANSFORM)` before `transform_apply`; Blender is Z-up so bounds
|
||||||
|
and bone rests are converted to `(x, z, -y)`. On project assets its counts match `audit`
|
||||||
|
exactly, but `audit` stays the stdlib/CI release gate and `inspect` never adapts a file.
|
||||||
|
|
||||||
|
- **Blender Skills Pack installed locally (2026-11-03):** the MIT `blender-skills` pack
|
||||||
|
(v1.3.0, 94 skills + a shared `references/` folder) is installed at
|
||||||
|
`.agents/skills/blender-skills/` for Blender asset work. `.agents/` is gitignored and
|
||||||
|
`.agents/.gdignore` keeps Godot's scanner out, so it never ships, exports or affects tests.
|
||||||
|
Skills link shared docs as `../references/<file>.md`, so keep the pack flat when updating:
|
||||||
|
`cp -a <pack>/.claude/skills/. .agents/skills/blender-skills/`. Pi gates project
|
||||||
|
`.agents/skills` behind project trust, so the trust prompt must be accepted before the
|
||||||
|
skills load.
|
||||||
|
|
||||||
|
- **Priority order (2026-11-03):** `TODO.md` now leads with the unversioned **Character
|
||||||
|
development** and **Local map development** tracks, then the versioned 0.10.0 release
|
||||||
|
candidate and later releases. Do not attach a placeholder version to an unversioned track;
|
||||||
|
assign one only when its scope enters a release. Character development is generator-first
|
||||||
|
(ADR-0015); local map development evaluates OSM/3D data, licensed reference imagery,
|
||||||
|
AI-assisted composition and development-only Gaussian splatting.
|
||||||
|
- **Commit/build discipline (2026-11-03):** commit each coherent verified change as it lands, not
|
||||||
|
only at milestone end, and push shareable commits. Every major, minor and patch version requires
|
||||||
|
a `CHANGELOG.md` entry, a local `tools/build_releases.sh` build and artifact/checksum
|
||||||
|
verification before tagging. CI tag builds are the second gate, not a substitute.
|
||||||
|
- **Gaussian splatting / photo-derived maps (unversioned local track):** expanded in
|
||||||
|
`TODO.md` "Gaussian splatting evaluation". Accepted photo channels are team captures,
|
||||||
|
permissioned user/volunteer photos and clearly licensed downloads; every set needs a
|
||||||
|
provenance/consent sidecar. Sparse web photos are reference/training supplements, not a splat
|
||||||
|
source. Five enhancement paths are ranked (reference/QA, texture bake, mesh proxy, distant
|
||||||
|
backdrop, single-facade user-photo supplement). Splats stay offline/development-only until the
|
||||||
|
ADR proves licence, privacy, Godot Compatibility/Web cost and mesh-based collision; no runtime
|
||||||
|
splat dependency in 0.10.0.
|
||||||
|
|
||||||
|
- **NPC model variety decision (2026-11-03, ADR-0015):** CC0 is acceptable for shipped
|
||||||
|
assets; CC-BY only with attribution in `CREDITS.md`; no service assets with redistribution
|
||||||
|
restrictions. The **procedural generator is the primary path**. Four crowd-variety specs were
|
||||||
|
prototyped with the existing builder (`npc_crowd_a` bun/slim/sunglasses, `npc_crowd_b`
|
||||||
|
bald/broad/beard/shoulder bag, `npc_crowd_c` coat/umbrella, `npc_crowd_d` hood/cup); all audit
|
||||||
|
clean at 4,550–5,258 tris / 4,356–5,086 verts. Prototype assets stay in
|
||||||
|
`tmp/npc-research/generator_proto/`; do not add them to the current release candidate.
|
||||||
|
**Quaternius is a reference, not a shipped base:** welding brings `Casual_Hoodie` from 12,518
|
||||||
|
to 3,199 verts and a 0.75 decimate to 2,423 verts / 4,654 tris, and its Idle/Walk clips read
|
||||||
|
well, but the T-pose 62-bone realistic rig/proportions do not match the arm-down 13-bone
|
||||||
|
contract. A contract rebuild is a no-go without a body proportion warp; a second
|
||||||
|
rig/animation path needs its own ADR. **MakeHuman is deferred** to a separate local pipeline:
|
||||||
|
the CC0 base is 19,158 verts / 36,972 tris and decimates to 3,780 / 6,468 at 0.35, but needs
|
||||||
|
retopology, a custom rig and project clothing. Research previews are committed under
|
||||||
|
`docs/blender_previews/sheet_npc_{sources_research,crowd_prototypes}.jpg`; the CC-BY entries
|
||||||
|
(CesiumMan/RiggedFigure, © Cesium) are attributed in `CREDITS.md` and are development-only.
|
||||||
|
|
||||||
|
- **0.10.0 balance protocol:** `docs/balance-acceptance.md` defines targets and
|
||||||
|
denominator/cohort rules for the 36 venue/difficulty/phase cells, seeds 1–100,
|
||||||
|
access/emergency error rates and matched calm vs emergency-first comparisons.
|
||||||
|
These are acceptance criteria, not measured results; sweeps/playtests remain open.
|
||||||
|
Inspection found `_read_npc_truth()` is type-derived, not behavior-derived;
|
||||||
|
the pending emergency fairness review must cover this oracle mismatch rather
|
||||||
|
than treating internal truth as independent evidence.
|
||||||
|
|
||||||
- The visual-alignment path/loader/panel/HUD work and the realism lighting pass are complete and
|
- 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`.
|
recorded in `DONE.md`; remaining visual items are the realism follow-ups in `TODO.md`.
|
||||||
|
- **Character mesh cap (2026-11-02):** the eight library variants now use **8,000 triangles /
|
||||||
|
8,000 glTF vertices** per character (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in
|
||||||
|
`tools/blender_assets.py`), up from 6,000/5,000. The frozen `npc_base` fallback keeps its
|
||||||
|
6,000/4,000 contract. This headroom is specifically for the remaining face, hand-grip and
|
||||||
|
clothing realism work; re-run the 24-NPC and Web/Compatibility budgets as geometry lands. Do not
|
||||||
|
add detail without measuring, and do not silently lower the cap back to 5,000.
|
||||||
- `PLAN.md` is a compact product/design document, not an implementation log. Historical control
|
- `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
|
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.
|
work in `TODO.md`, not the plan.
|
||||||
@@ -23,27 +109,227 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
head width uses skin vertices weighted to Head in y 1.55–1.81; clothing radius is the cloth
|
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,
|
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.
|
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
|
- Post-realism work is split by theme, not by date: finished items are archived in `DONE.md`, and
|
||||||
are archived in `DONE.md`, and `TODO.md` carries three planned workstreams (NPC gear fit,
|
`TODO.md` carries the 0.x gap register and milestone roadmap. New work belongs there, not in
|
||||||
venue geometry + committed gallery, Forward+ desktop preset). New visual work should be added
|
a revived historical plan document.
|
||||||
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
|
- Graphics v0.6.0 plan (completed 2026-11-01; ADR-0013): NPCs move to Blender-authored skinned meshes. `tools/npc_pipeline.py` builds
|
||||||
(`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()`
|
`assets/models/generated/npc_base.glb`; `NpcAvatar` keeps the procedural `_build_body()`
|
||||||
fallback. Preserve `Rig/Head`, the limb node names, and the seed-deterministic
|
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
|
`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).
|
`/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.7.2 stable (official editor build, `~/.local/bin/godot`). The system's preinstalled `godot-runner` 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`.
|
||||||
- Eventing: no autoload signal bus. `ShiftCoordinator` owns an `EventBus` instance (`src/core/event_bus.gd`) passed to subsystems; subsystems emit only when a bus is assigned. Autoloads at parse time are unreliable under `--script`.
|
- Eventing: no autoload signal bus. `ShiftCoordinator` owns an `EventBus` instance (`src/core/event_bus.gd`) passed to subsystems; subsystems emit only when a bus is assigned. Autoloads at parse time are unreliable under `--script`.
|
||||||
- Static signals are not available in Godot 4.3 GDScript ("Expected func or var after static") — do not use them.
|
- Static signals are not available in Godot GDScript ("Expected func or var after static") — do not use them.
|
||||||
- GDScript lambdas capture local variables **by value**; mutating a captured local inside a lambda does not affect the outer variable. Use member functions for accumulators in tests.
|
- GDScript lambdas capture local variables **by value**; mutating a captured local inside a lambda does not affect the outer variable. Use member functions for accumulators in tests.
|
||||||
- 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.
|
||||||
|
|
||||||
|
- **Mouse look (v0.7.3):** guard look is handled in `GuardController._input()` (before GUI), not `_unhandled_input`; focus/click-back recaptures the cursor and the HUD is made mouse-transparent recursively. Regression tests cover yaw/pitch and HUD filters.
|
||||||
|
- **v0.9.0 production pass:** CI/release, local dev and project now use Godot 4.7.2; the version-adaptive light falloff remains for older editors. Audio settings are `master_volume`/`sfx_volume`/`ambience_volume`; accessibility has `reduce_motion` and `captions_enabled` (HUD log is the caption surface). Budget smoke is 24 NPCs (6.94 ms headless). Coat/dress stride is scaled to 0.76 to reduce hem clipping. Localization remains English-table based; pseudo-locale/VO/music are post-0.10.
|
||||||
|
- **Radio submit fix (v0.8.1):** `RadioPanel.action_button` now connects `pressed` for normal reports; `button_down/up` remains exclusive to emergency hold-to-confirm. Regression test covers both modes.
|
||||||
|
- **Campaign/v0.8.0:** `Campaign` owns Hollywood → Joke Shop → TV Studio progression. `campaign.json`, `shift_results.json`, `settings.json` and versioned `shift_state.json` persist separately; pause saves a checkpoint, review Continue advances, Settings resets with confirmation. Difficulty keys are `easy/normal/hard` (`ai_difficulty`); scripted QA bypasses tutorial unless `--tutorial` is passed. Help is searchable and generated from `InputBindings.REMAPPABLE`.
|
||||||
|
- **Tutorial (0.8.0 work):** `docs/tutorial.md` is the eight-beat design; `src/game/tutorial.gd` is the pure state machine, `TutorialOverlay` is the non-blocking card, and `GameManager` wires it with a safe visitor, suppressed spawns, persisted completion/skip and Settings replay. Scripted test/capture runs bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers completion/replay/skip.
|
||||||
|
- **Pre-build NPC revisit (2026-11-02):** the final-context harness is
|
||||||
|
`src/test/capture_npc_review.gd` (8 library models, 3 distances, 2 venues,
|
||||||
|
day/night). It found two P1 gear-fit issues: the lanyard badge was hidden
|
||||||
|
under the jacket and the firefighter helmet brim sat over the eyes. Both are
|
||||||
|
fixed at runtime with `NpcAvatar.GEAR_OFFSETS` (`lanyard` forward 4 cm,
|
||||||
|
`firefighter_helmet` up 3 cm); the authored GLBs and the fit report remain
|
||||||
|
asset-space measurements. The guard model is staged through the review-only
|
||||||
|
`character_override` because it has no spawned NPC role. **Signed off
|
||||||
|
2026-11-02:** 96 final-context captures, no P0; lanyard/helmet P1 fixes; the
|
||||||
|
backpack straps, caps, low-poly hands/faces and optional close-range
|
||||||
|
flashlight wash were accepted or deferred. The 24-NPC budget is 6.94 ms, the
|
||||||
|
gallery is 13 good / 0 bad, the screen-design judge is 13/13 and a clean
|
||||||
|
portrait measured a 0.732 vision gap against the photoreal concept. No
|
||||||
|
release build, version bump or tag was created at this gate.
|
||||||
|
- **NPC texture resolution (0.9.5, 2026-11-02):** `tools/npc_pipeline.py` now generates 256²
|
||||||
|
albedo/normal/ORM maps instead of 128² (AI shirt/trousers are resized to 256). The base and all
|
||||||
|
eight library GLBs plus AO maps were rebuilt; texture files total ~0.5 MB, eight GLBs ~5.75 MB,
|
||||||
|
and the 24-NPC smoke is 6.94 ms.
|
||||||
|
- **0.9.5 texture/UI audit (2026-11-02):** all 21 screen-art textures are
|
||||||
|
1280×720 and referenced by their screens; access cards are 256×128 and HUD
|
||||||
|
icons 64×64, with no oversized or unused runtime texture. The 24-NPC smoke now
|
||||||
|
reports **168 meshes / 131,616 triangles / 6.94 ms** (50 ms budget). Compressed
|
||||||
|
payloads are picks **24.55 MB**, UI art **22.67 MB**, NPC maps **0.48 MB**.
|
||||||
|
Legacy root textures remain only for `generate_textures.py` and the asset test.
|
||||||
|
- **Environment surface audit (0.9.5, 2026-11-02):** `capture_realism.gd` gained
|
||||||
|
`surfaces` (Hollywood pavers/asphalt/limestone/facade) and `surfaces_joke`
|
||||||
|
(neighbour awning, canopy, pavers) stages with the HUD hidden. The Walk of Fame
|
||||||
|
band now uses `T_CONCRETE` as dark terrazzo; the neighbour awning uses a neutral
|
||||||
|
tint (`0.62`) and 4× UV so the red/cream stripes survive; the skyline shader
|
||||||
|
fades its lower 42 m toward a per-phase haze colour. Brick/asphalt picks needed
|
||||||
|
no replacement. No seams or broken UV scales found.
|
||||||
|
- **NPC fabric UV/AO (0.9.5, 2026-11-02):** cloth/pants use
|
||||||
|
`Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`, and `NpcAvatar` skips the baked per-character
|
||||||
|
AO on those two slots. The 256² AO was magnifying into large blotches; the fabric now reads as a
|
||||||
|
fine weave. Skin/hair/gear keep baked AO. Test `test_fabric_uv_scale_avoids_the_baked_ao_pattern`
|
||||||
|
guards this. Current suite is 272/272.
|
||||||
|
- **NPC visual baseline (0.9.4, 2026-11-02):** `src/test/capture_npc_baseline.gd` is the
|
||||||
|
neutral-studio 8-variant × 6-view baseline (front, three-quarter, profile, back, full body,
|
||||||
|
crowd). It uses a fixed orbit camera, mid-grey studio backdrop, key/fill lights, no HUD/crowd/animation;
|
||||||
|
raw frames stay in `tmp/`, and `tools/npc_baseline.py sheet` builds the committed
|
||||||
|
`sheet_npc_baseline.jpg`. Criteria and measurements are in `docs/npc-visual-review.md`. The first
|
||||||
|
ranked gaps were: tourist photo prop duplicates the backpack silhouette; shallow/faceted face;
|
||||||
|
backpack straps read as rigid bars. The tourist prop now hides outside photo/film poses and the
|
||||||
|
backpack straps were refit as flat forward bands (GLB + AO rebuilt); the face remains P1. Before/
|
||||||
|
after sheet: `sheet_npc_baseline_compare.jpg`.
|
||||||
|
- **NPC proportion audit (0.9.4, 2026-11-02):** `tools/npc_proportions.py` measures the exported
|
||||||
|
GLBs (height, skin-head size, upper-chest shoulder silhouette, pants hip width, Spine-to-shoulder
|
||||||
|
torso, Spine-to-ground leg, shoulder-to-hand arm, shoe foot length). All eight variants pass the
|
||||||
|
stylized guide in `docs/npc-proportion-audit.md`; head ~0.190 (upper 0.22) and broad-build
|
||||||
|
shoulders ~0.370/hip ~0.225 are near the upper bounds, not outliers. No skeleton/mesh rescale was
|
||||||
|
needed; run the report after any body change.
|
||||||
|
- **NPC neck/shoulder transition (0.9.4, 2026-11-02):** the body graph uses a fuller neck base
|
||||||
|
(1.43 m, radius 0.074/0.072), a taller/thicker neck top and a shoulder root lowered from 1.39 to
|
||||||
|
1.37 m. A final triangulate + tiny-face delete in `build_character` removes zero-area triangles
|
||||||
|
that the new neck branch exposed in jacket/hoodie. All eight GLBs and AO maps were rebuilt;
|
||||||
|
`npc_proportions.py` still passes and `sheet_npc_baseline_compare-body.jpg` is the review image.
|
||||||
|
- **NPC face-depth pass (0.9.4, 2026-11-02):** recessed the sclera to `ey=-0.134` and added a
|
||||||
|
thin upper lid; removed the separate jaw/cheek spheres and instead sculpt front head vertices
|
||||||
|
(`_shape_head_mesh`) around a chin plate and cheekbones. This avoids dark AO intersection rings.
|
||||||
|
Moved lips forward to stay on the reshaped surface. Latest review sheet:
|
||||||
|
`sheet_npc_baseline_compare-face.jpg`; max character is now 4,754 tris / 4,574 verts.
|
||||||
|
- **NPC torso/hip transition (0.9.4, 2026-11-02):** pelvis radius 0.165→0.168, waist
|
||||||
|
0.150→0.146, chest 0.170→0.168, upper chest 0.172→0.170. This is a subtle shirt taper, not a
|
||||||
|
skeleton change; all proportions pass and `sheet_npc_baseline_compare-torso.jpg` records it.
|
||||||
|
Max character is 4,754 tris / 4,584 verts.
|
||||||
|
- **NPC footwear pass (0.9.4, 2026-11-02):** added `ShoeToe`/`ShoeHeel` spheres to the casual shoe
|
||||||
|
and `BootToe`/`BootHeel` to responder boots; both stay above the sole/boot contact plane. Current
|
||||||
|
counts are 4,482–5,474 tris / 4,260–5,494 verts under 8,000/8,000. Sheet:
|
||||||
|
`sheet_npc_baseline_compare-shoes.jpg`.
|
||||||
|
- **NPC hand-item forward offset (0.9.4, 2026-11-02):** `NpcAvatar.HAND_ITEM_FORWARD =
|
||||||
|
Vector3(0,0,0.04)` is added in `_attach_gear()` for every `GEAR_ATTACH` hand item after the
|
||||||
|
head/chest offsets. Hand gear is authored around the wrist proxy; without this, phone,
|
||||||
|
flashlight, cup and clipboard were partly inside the palm. Fingers are still open, so true
|
||||||
|
curl/thumb contact and umbrella/sign grips stay open. Comparison:
|
||||||
|
`sheet_npc_hands_grip_compare.jpg`; regression assertions cover camera and flashlight positions.
|
||||||
|
- **NPC headgear clearance (0.9.4, 2026-11-02):** after the face-detail pass, the 96
|
||||||
|
final-context frames confirm the cap, firefighter helmet, hood and sunglasses clear the refined
|
||||||
|
brow/eyes; `blender_assets.py fit` stays clean, so no gear was rebuilt. The broad helmet brim is
|
||||||
|
intentional turnout language, not a fit failure.
|
||||||
|
- **NPC face-detail pass (0.9.4, 2026-11-02):** Nose tip radius reduced to 0.022 with ala wings,
|
||||||
|
the two dark lip boxes became one shallow `Mouth` sphere (two blobs read as a moustache), and the
|
||||||
|
ears gained a dark `EarInner` concha. All eight rebuilt; current counts are 4,226–5,218 tris /
|
||||||
|
4,119–5,240 verts under the 8,000/8,000 cap. Sheet:
|
||||||
|
`sheet_npc_baseline_compare-face-detail.jpg`.
|
||||||
|
- **NPC hand pass (0.9.4, 2026-11-02):** palm radius 0.046 at z .742, scale (.80,.62,.88);
|
||||||
|
fingers are `tube` paths from z .758 to .681–.694 with tip spheres, thumb is a bent tube. The
|
||||||
|
optional hand capture is `capture_npc_baseline.gd -- --view=hand` (8 frames, baseline stays 48).
|
||||||
|
`npc_firefighter` is at 5,240 verts after the face-detail pass; the library cap is 8,000 (see
|
||||||
|
the character-mesh-cap decision above), so grip/clothing detail still has headroom. Grip offsets
|
||||||
|
remain open.
|
||||||
|
- **UI flow smoke (2026-11-02):** `smoke_ui_flows.gd` boots the real scene and
|
||||||
|
exercises inspection allow/deny, normal vs emergency radio, Help filtering,
|
||||||
|
wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.6
|
||||||
|
handoff; it complements the unit tests that only exercise panels.
|
||||||
|
- **Destructive actions (2026-11-02):** `SettingsPanel._open_destructive_dialog()`
|
||||||
|
owns one confirmation instance for reset/replay; repeated presses focus the
|
||||||
|
existing dialog, OK emits once, Cancel is inert. Emergency send remains the
|
||||||
|
RadioPanel hold path; `_holding` clears before submit so button-up cannot
|
||||||
|
double-send. Tests cover both.
|
||||||
|
- **Font/layout limits (2026-11-02):** HUD scale is clamped to 0.75–1.75 and only
|
||||||
|
applies to HUD labels. `Hud.set_target_label()` measures the scaled font, widens
|
||||||
|
the chip to 180–420 px and re-measures after a scale change; top labels clip.
|
||||||
|
`UiStyles.apply_button()` sets `clip_text` + ellipsis so long localized labels
|
||||||
|
cannot force a panel past the viewport. Do not replace these with Control.scale.
|
||||||
|
- **Modal close (2026-11-02):** `GameManager._close_top_modal()` is the single
|
||||||
|
Esc/right-click path for radial, inspection, wand, radio, timeline, Help,
|
||||||
|
Settings, Pause and review. Settings remembers whether it came from the menu and
|
||||||
|
restores it on apply/cancel; `_refresh_freeze()` recaptures the mouse and
|
||||||
|
`_show_start_menu()` keeps it visible. Do not add a new modal without adding it
|
||||||
|
to this close path.
|
||||||
|
- **UI focus (2026-11-02):** `src/game/ui/ui_focus.gd` links visible/enabled
|
||||||
|
controls into a wrapping ring; each modal sets default focus on open. Start menu
|
||||||
|
uses its own focus-target list so Continue, venue rows and footer actions share
|
||||||
|
one ring. `GuardController._ensure_gamepad_bindings()` adds A/B to
|
||||||
|
`ui_accept`/`ui_cancel` so pad-only users can confirm/cancel; hidden Continue and
|
||||||
|
disabled timeline buttons never enter the ring.
|
||||||
|
- **Button states (2026-11-02):** `src/game/ui/ui_styles.gd` owns the five action-button
|
||||||
|
states and font colours for the dark session/UI shell. Use `UiStyles.apply_button()`
|
||||||
|
for every new `Button`; CheckButton/OptionButton keep the engine theme. Timeline
|
||||||
|
Previous/Next disable at the ends; F1 tutorial skip is keyboard-only by design.
|
||||||
|
- **Version and roadmap renumber (2026-11-02):** source was **0.9.3** at the UI
|
||||||
|
polish milestone, **0.9.4** after the NPC pass, now **0.9.5** after the texture
|
||||||
|
and material pass, and **0.9.6** as the integration checkpoint. Next work is
|
||||||
|
the **0.10.0** minor release candidate. `TODO.md` plans only 0.x patch/minor increments; no
|
||||||
|
major-version scope is defined yet. The `v0.9.6` tag is the release trigger; no
|
||||||
|
tag was created for 0.9.3.
|
||||||
|
- **Priority change (2026-11-02):** the map optimization, pre-build NPC revisit,
|
||||||
|
0.9.3 UI polish, 0.9.4 NPC visual optimization, 0.9.5 texture/material pass and
|
||||||
|
0.9.6 integration are complete and in `DONE.md`. 0.10.0 release-candidate work
|
||||||
|
is next. The source version is 0.10.0-rc.1.
|
||||||
|
- **0.10.0-rc.1 build (2026-11-03):** clean worktree `a9658ec` produced five
|
||||||
|
artifacts (Linux 161.5 MB, Windows 197.2 MB, macOS 148.0 MB, Web 97.8 MB, HQ
|
||||||
|
241.6 MB); Web/HQ include player docs, SHA-256 manifests are local, Linux/HQ
|
||||||
|
launch headless and Web files serve HTTP 200. Not published: external playtest,
|
||||||
|
AI/legal approval, macOS signing and the Windows/macOS matrix are still open.
|
||||||
|
- **Release/legal blockers (0.10.0 work, 2026-11-03):** macOS remains unsigned
|
||||||
|
(quarantine instructions only); AI image provider terms/commercial rights and
|
||||||
|
external storefront/rating approval are not cleared; external playtests and the
|
||||||
|
full hardware matrix are pending. `CREDITS.md`, `PRIVACY.md`, `docs/accessibility.md`
|
||||||
|
and `docs/known-limitations.md` record these explicitly rather than claiming
|
||||||
|
approval. Do not tag 0.10.0 as final until these are accepted or cleared.
|
||||||
|
- **Save migration (0.10.0 work, 2026-11-03):** fixtures in `src/test/fixtures/saves/`
|
||||||
|
cover 0.8 settings/campaign/history and a 0.9.0 checkpoint. `_load_json` uses
|
||||||
|
`JSON.new().parse()` so corrupt player files fall back silently instead of
|
||||||
|
logging a parse error; atomic writes leave the old file intact when the temp
|
||||||
|
path cannot be written. The migration suite is 9 tests; total 272.
|
||||||
|
- **0.9.6 integration (2026-11-03):** no gameplay/content changes. A clean
|
||||||
|
detached worktree at `f5b8ef2` rebuilt Linux/Windows/macOS/Web/HQ without the
|
||||||
|
dirty tree's ignored `tmp/`/`build/` payload; the in-repo build was 1.6–3.1 GB
|
||||||
|
per artifact because those folders were packed, so always build release
|
||||||
|
artifacts from a clean checkout/worktree. Verification: 263/263 tests,
|
||||||
|
101/101 Python, 14 smokes, 24-NPC 6.90 ms / 168 meshes / 131,616 triangles,
|
||||||
|
gallery 21/0, screen judge 21/21, realism judge overall 2.1 / gap 0.291.
|
||||||
|
- **Release v0.9.6 (2026-11-03):** commit `4bb2896` tagged `v0.9.6`; Gitea
|
||||||
|
Actions run 1296 passed checks and the full build and published seven assets
|
||||||
|
(Linux, Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page:
|
||||||
|
`https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.6`. This is
|
||||||
|
the integration checkpoint before 0.10.0; do not reuse the tag.
|
||||||
|
- **Release v0.9.5 (2026-11-02):** commit `4bc6390` tagged `v0.9.5`; Gitea Actions run
|
||||||
|
922 passed checks and the full build and published seven assets (Linux, Windows,
|
||||||
|
macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page:
|
||||||
|
`https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.5`. Do not
|
||||||
|
reuse the tag; future fixes need a new patch tag.
|
||||||
|
- **Release v0.9.4 (2026-11-02):** commit `a57b86c` tagged `v0.9.4`; Gitea Actions
|
||||||
|
run 916 passed checks and the full build and published seven assets (Linux,
|
||||||
|
Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page:
|
||||||
|
`https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4`. Do not
|
||||||
|
reuse the tag; future fixes need a new patch tag.
|
||||||
|
- **Hollywood landmark map (2026-11-02):** the default venue is the real El Capitan
|
||||||
|
Entertainment Centre block at 6840 Hollywood Blvd, not a generic brick entrance. Its
|
||||||
|
reference/mapping notes live in `docs/hollywood-boulevard-map.md`; the map-budget test
|
||||||
|
(`test_hollywood_geometry_stays_inside_the_map_budget`) currently measures 58,884 triangles
|
||||||
|
across 409 meshes. The facade uses the shared smooth limestone (`stone_steps`) texture; the
|
||||||
|
noisier granite pick was removed.
|
||||||
|
- **Light falloff gotcha (2026-11-02):** in Godot 4.7 `omni_attenuation = 0.0` is constant
|
||||||
|
falloff, not physical falloff. It made the night facade a flat yellow wall and the street
|
||||||
|
lamps wash the set. All local lights now use `1.0`; the Hollywood facade was retuned with
|
||||||
|
0.50/0.28 washes, 1.3 sconces and 2.2 street lamps; the Joke Shop uses 1.8 sconces,
|
||||||
|
2.8 canopy downlights, 3.0 entrance, 1.35 ground uplights and 2.2 sign. Flashlight spot
|
||||||
|
attenuation is also `1.0`. Do not reintroduce `0.0` as a "modern" branch.
|
||||||
|
- **Joke Shop map (2026-11-02):** the venue is now a 27 m Sunset Strip club based on The
|
||||||
|
Comedy Store at 8433 Sunset Blvd, not the old brick alley. `docs/joke-shop-map.md` carries
|
||||||
|
the OSM/Commons references and simplifications; `docs/screens/concepts/joke_shop/` holds the
|
||||||
|
new day/night/across-street frames. The map-budget test measures 22,680 triangles / 454
|
||||||
|
meshes. The hill behind the club is a custom hand-built ramp with an unshaded, fog-disabled
|
||||||
|
shader: a rotated `PlaneMesh` was edge-on and scene fog erased it from the street camera.
|
||||||
|
Local light names are `JokeSign`, `JokeCanopy_*`, `JokeEntrance`, `JokeSconce_*`,
|
||||||
|
`JokeGround_*`; `set_night_mode()` owns their energies and the hill tint.
|
||||||
|
The latest local realism judge reports a 0.454 reference gap on the night entrance;
|
||||||
|
the concept is photoreal while the map keeps stylized materials, so texture/foliage
|
||||||
|
detail stays with the 0.9.5 texture pass rather than the map milestone.
|
||||||
|
- **Docs validation (2026-11-02):** `python3 tools/check_docs.py` requires a README
|
||||||
|
only in directories containing tracked files, verifies local links and requires
|
||||||
|
every tracked image in a folder to be linked from its README. Gitignored
|
||||||
|
generated files (notably `docs/screens/realism/`) are exempt; the tool also has
|
||||||
|
unit tests in `tools/test_check_docs.py`.
|
||||||
|
- **Documentation ownership (2026-11-01):** `docs/architecture.md` §12 defines the source-of-truth map and consolidation rules. Obsolete/historical plan documents are removed rather than kept; current work belongs in `TODO.md`. README/development-status/Blender AO/Forward+ stale claims were corrected. The 0.x gap register and roadmap live in `TODO.md`.
|
||||||
- **Vision judge (v0.7.2):** `tools/openrouter_judge.py` reads `/api/v1/models`, filters image-input models with `response_format`, and ranks by advertised prompt/image/completion prices. `--model auto` (default) uses the cheapest (`z-ai/glm-5.3-flash` on 2026-11-01) with fallbacks; explicit `--model`/`GIG_LINE_JUDGE_MODEL` wins. Auth errors abort, provider/model errors fall through. Full screen judge 13/13 match, 0 diff; A/B judge ~$0.006.
|
- **Vision judge (v0.7.2):** `tools/openrouter_judge.py` reads `/api/v1/models`, filters image-input models with `response_format`, and ranks by advertised prompt/image/completion prices. `--model auto` (default) uses the cheapest (`z-ai/glm-5.3-flash` on 2026-11-01) with fallbacks; explicit `--model`/`GIG_LINE_JUDGE_MODEL` wins. Auth errors abort, provider/model errors fall through. Full screen judge 13/13 match, 0 diff; A/B judge ~$0.006.
|
||||||
- **Desktop HQ preset (v0.7.1):** `packaging/desktop_hq/override.cfg` switches a
|
- **Desktop HQ preset (v0.7.1):** `packaging/desktop_hq/override.cfg` switches a
|
||||||
copied executable to Forward+; `Materials.forward_plus_active()` gates
|
copied executable to Forward+; `Materials.forward_plus_active()` gates
|
||||||
@@ -54,13 +340,13 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
binaries + override into `gig-line-desktop-hq-<version>.zip`;
|
binaries + override into `gig-line-desktop-hq-<version>.zip`;
|
||||||
`publish_release.sh` uploads it when present. A/B report:
|
`publish_release.sh` uploads it when present. A/B report:
|
||||||
`docs/screens/realism/2026-11-01-forward-plus/` (entrance gap 1.568→1.187).
|
`docs/screens/realism/2026-11-01-forward-plus/` (entrance gap 1.568→1.187).
|
||||||
- **Local Godot toolchain (2026-11-01):** `~/.local/bin/godot` is 4.7.2 stable;
|
- **Godot toolchain (updated 2026-11-01):** local `~/.local/bin/godot`, CI and
|
||||||
4.3 stable is kept as `~/.local/bin/godot-4.3.stable`. Export templates for
|
`project.godot` now use Godot 4.7.2; 4.3 remains only as a local backup binary.
|
||||||
4.7.2 are installed under `~/.local/share/godot/export_templates/4.7.2.stable`
|
Export templates for 4.7.2 are installed under
|
||||||
(4.3 remains). The project and CI still pin 4.3 in `project.godot`/the Gitea
|
`~/.local/share/godot/export_templates/4.7.2.stable`. Godot 4.7 writes
|
||||||
workflow; 4.7.2 imports, runs the 231-test suite, session-shell smoke and a
|
`*.gd.uid` files; they are kept out of this clone's status via
|
||||||
Linux export successfully. Godot 4.7 writes `*.gd.uid` files; they are kept
|
`.git/info/exclude`. CI downloads the binary/templates directly; Gitea
|
||||||
out of this clone's status via `.git/info/exclude` until the project upgrades.
|
Actions cache is currently unreliable (see CI cache evaluation below).
|
||||||
- **Blender invocation (v0.6.0):** `flatpak run org.blender.Blender --background --python
|
- **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`.
|
/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
|
The flatpak cannot read `/tmp`; use absolute repo paths. `assets/models/npc/.gdignore` is
|
||||||
@@ -87,11 +373,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
**Mockup copies:** 21 design references live in `assets/images/ui/`; `docs/screens/designs/`
|
**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.
|
remains the reference-only copy. `tools/screen_designs.py` reads/writes the runtime root.
|
||||||
|
|
||||||
## Handoff (read this first)
|
## Handoff — historical snapshot (2026-11-01; superseded by Current State / Plan vs code)
|
||||||
|
|
||||||
- **Status (2026-11-01, after the venue-geometry pass):** v0.7.0 is released. Post-v0.6.0 the
|
- **Status (2026-11-01, after the venue-geometry pass):** v0.7.0 was released at that point. Post-v0.6.0 the
|
||||||
realism pass, the visual-alignment pass and the NPC face/hand pass all landed. Headless
|
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**,
|
**272/272**, Python tooling **108/108**, `tools/check_screenshots.sh` **21 good / 0 bad**,
|
||||||
`screen_designs.py compare` 11 good / 0 bad, `scene_concepts.py audit` all GOOD,
|
`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).
|
`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
|
`PLAN.md` is compact; README/architecture track the current controls, asset pipeline, CI and
|
||||||
@@ -106,7 +392,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
gpt-5-image-mini rejects (HTTP 400); pass `--request-size 1024` for 1:1 roles.
|
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
|
`generate --role tile` already applies `make_seamless`, so a separate
|
||||||
`seamless` pass is redundant. Added to `tools/npc_pipeline.py`: `prepare`
|
`seamless` pass is redundant. Added to `tools/npc_pipeline.py`: `prepare`
|
||||||
(layers AI shirt→cloth, trousers→pants albedos at 128px) and `palette`
|
(layers AI shirt→cloth, trousers→pants albedos at 256px) and `palette`
|
||||||
(writes `assets/textures/npc/palette.json` via `model_pipeline.dominant_colors`).
|
(writes `assets/textures/npc/palette.json` via `model_pipeline.dominant_colors`).
|
||||||
`write_slot_textures()` now skips the albedo for slots with an AI override
|
`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 /
|
(normal/ORM stay procedural). Blender `build` re-run: GLB audit ok, 1204 tris /
|
||||||
@@ -141,7 +427,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
`npc_pipeline.py` + `asset_pipeline.py` and the confirmed-asset locations.
|
`npc_pipeline.py` + `asset_pipeline.py` and the confirmed-asset locations.
|
||||||
|
|
||||||
- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at
|
- 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).
|
`/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.7.2).
|
||||||
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**.
|
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**.
|
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.
|
||||||
@@ -162,15 +448,19 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
material (`baseColorTexture` + `metallicRoughnessTexture`, NEAREST sampling). Colours are sRGB in
|
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
|
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.
|
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
|
- **Mockups complete (v0.3.2, updated 0.9.3):** `docs/screens/designs/` holds AI references for
|
||||||
catalogue screens. Only the six staged captures (01–06) are compared/judged; the other 13
|
all 21 catalogue screens, and every entry now has a committed current-build capture.
|
||||||
have no `capture`, so adding staged captures for the wand panel, timeline and escalation HUD
|
`capture_screenshots.gd` covers scenes 01–13, `capture_ui_states.gd` covers UI states 14–21,
|
||||||
would extend automated visual checks. `generate` now calls `_load_env_file()`.
|
and `capture_prop_gallery.gd` covers the prop gallery. `generate` now calls `_load_env_file()`.
|
||||||
- **Staged captures (v0.3.3):** `capture_screenshots.gd` now emits nine shots. States 7–10 switch
|
- **Staged captures (v0.3.3, updated 2026-11-02):** `capture_screenshots.gd` emits the scene
|
||||||
to The Joke Shop at elapsed 620 s (EVENING has routine spawns; LATE has none), retry the wand
|
gallery; states 7–10 switch to The Joke Shop at elapsed 620 s, retry the wand until it alerts,
|
||||||
until it alerts, then jump to 760 s after staging for night lighting. The timeline needs
|
then jump to 760 s after staging for night lighting. LATE now has its own routine pool.
|
||||||
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents. The escalation
|
Shot 12 closes any stale wand/inspection modal before the venue beauty shot (the wand fallback
|
||||||
HUD mockup is third-person now; `judge` still classes it `review` (no diff).
|
can leave `hud.show_wand` open without `wand_open`). The timeline needs
|
||||||
|
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents.
|
||||||
|
`capture_ui_states.gd` adds 14–21; crowd panic stages a group 70 frames into the flee and
|
||||||
|
responder arrival parks the responder with the door locked. `check_screenshots.sh` writes all
|
||||||
|
captures to `docs/screenshots/` before `compare`; judge is 21/21 match.
|
||||||
- **Prop collision (v0.4.0):** `_add_prop_model()` fits a `StaticBody3D` from the model bounds
|
- **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
|
(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
|
enable avoidance and move with the safe velocity from `velocity_computed`. The navmesh stays a
|
||||||
@@ -190,7 +480,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
joke_shop 700 s / studio 700 s; `--stages=` subset) and swaps textures by the material'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
|
*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).
|
`_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
|
**Gotcha:** `elapsed_s = 620` is *Evening* (dusk), 700+ is Late. Godot 4.7
|
||||||
`StandardMaterial3D` has no `fog_enabled`; the skyline is a `ShaderMaterial` with
|
`StandardMaterial3D` has no `fog_enabled`; the skyline is a `ShaderMaterial` with
|
||||||
`render_mode unshaded, cull_disabled, fog_disabled` + `ALPHA_SCISSOR_THRESHOLD` so the
|
`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
|
panorama's own haze shows. Backdrop plane is 90 m tall with 2 UV repeats (~200×90 m per
|
||||||
@@ -229,7 +519,8 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
ridge, cheekbones, nose bridge/tip, ears with lobes and split upper/lower lips; the new
|
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.
|
`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.
|
`_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
|
Budget work at the time: `CHAR_MAX_VERTS` 5000 (the library cap is now 8000; see the
|
||||||
|
character-mesh-cap decision), body decimation 0.26, upper lids are
|
||||||
boxes rather than spheres, and knuckle spheres/nostrils were cut to stay inside it.
|
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`
|
**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
|
surface (face) and of the `rubber` sole verts (toes) — all 8 avatars are +Z, so any
|
||||||
@@ -240,11 +531,13 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
truth), sources in `assets/models/blender_src/` (`.gdignore`d), and review sheets
|
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
|
`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`
|
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
|
(same mesh node name, bones at the same rest positions, five tint slots); the `npc_base`
|
||||||
6000/4000 budget only because of body decimation (0.26) + `smart_project` at 80°: glTF vertex
|
fallback stayed under its frozen 6000/4000 budget 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
|
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
|
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.
|
~6.9 ms of a 50 ms frame; on 2026-11-02 the library cap became 8000/8000 while
|
||||||
|
`npc_pipeline.MAX_VERTICES`/`MAX_TRIANGLES` (base mesh) stayed frozen at 4000/6000.
|
||||||
**Gotchas:** `bisect_plane` and thin bevels leave zero-area triangles (Godot warns "non-finite
|
**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.
|
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
|
Fixed-colour detail materials carry a 4×4 texture because `_apply_outfit_materials()` sets
|
||||||
@@ -263,7 +556,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
helmet is lifted 2 cm above the brow. `test_npc_character` guards head/back gear in Godot.
|
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
|
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.
|
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.
|
- **Venue geometry (v0.6.1+, Joke Shop updated 2026-11-02):** `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 now uses `architecture: "comedy_store"` with `_build_comedy_store_building()`, `SunsetHillside`, `SunsetNeighbours` and `SunsetAcrossStreet`; the old `_build_alley_dressing()` is gated off. TV Studio keeps the stone facade, bulb canopy, car paint and barrier placements. Gallery shot 12 now maps to `joke_shop_entrance_night`; shot 13 stays `studio_stage_door_night`.
|
||||||
- **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.
|
- **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
|
- **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
|
inverse look direction) and hides the blue `FocusCircle`; the old code used the wrong yaw and
|
||||||
@@ -395,7 +688,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
it. The body camera is a real flag (`GameManager.body_camera_on`, `Hud.set_body_camera`), off by
|
it. The body camera is a real flag (`GameManager.body_camera_on`, `Hud.set_body_camera`), off by
|
||||||
default, `B` toggles it, and a formal warning arms it and writes a `body_camera` incident event.
|
default, `B` toggles it, and a formal warning arms it and writes a `body_camera` incident event.
|
||||||
Blocking also calls `_station_between()` to physically place the guard 0.9 m from the NPC toward
|
Blocking also calls `_station_between()` to physically place the guard 0.9 m from the NPC toward
|
||||||
the entrance. `_request_reason()` logs the NPC's seeded `dialogue_lines` pick.
|
the entrance. `_request_reason()` uses the authored `reason` context (falling back to seeded `dialogue_lines`) and stores the pick for the timeline.
|
||||||
- **NPC navigation was dead (fixed 2026-10-31):** avatars never moved in the live game, for three
|
- **NPC navigation was dead (fixed 2026-10-31):** avatars never moved in the live game, for three
|
||||||
stacked reasons. (1) `agent.target_position` was re-assigned every physics frame, which
|
stacked reasons. (1) `agent.target_position` was re-assigned every physics frame, which
|
||||||
invalidates the path so `get_next_path_position()` keeps returning the current point — only set
|
invalidates the path so `get_next_path_position()` keeps returning the current point — only set
|
||||||
@@ -434,6 +727,9 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
`unknown`. `--dry-run`, `--json`, `--only` supported. The prompt explicitly ignores art style,
|
`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.
|
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`.
|
Python tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs`.
|
||||||
|
- **String newlines (2026-11-02):** `content/strings.en.json` needs one escaped
|
||||||
|
newline (`\n`), not two. Two backslashes reached the HUD as literal `\n` in
|
||||||
|
the shift status and wand panel; `test_strings_format` now guards this.
|
||||||
- **String formatting safety (2026-10-31):** `Strings.placeholder_count()` counts `%` specs
|
- **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
|
(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 `%`,
|
(instead of raising an engine error), and `validate()` lints the table for trailing `%`,
|
||||||
@@ -572,9 +868,13 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
removed so hosts scanning both paths do not double-run.
|
removed so hosts scanning both paths do not double-run.
|
||||||
- Venue cycling: `V` switches Hollywood Blvd → The Joke Shop → TV Studio and restarts the shift.
|
- Venue cycling: `V` switches Hollywood Blvd → The Joke Shop → TV Studio and restarts the shift.
|
||||||
- Venue door-staff dialogue: `VenueScenario.door_staff_line(context, rng)` and GameManager logging
|
- Venue door-staff dialogue: `VenueScenario.door_staff_line(context, rng)` and GameManager logging
|
||||||
(id/ticket/wand-check, refusal, prohibited-item) — Joke Shop flavor.
|
(wristband, ID/ticket, wand-check, refusal, prohibited-item). Hollywood uses a wristband line,
|
||||||
- CI cache: `.gitea/workflows/build.yml` no longer uses `actions/cache` (the runner can't reach
|
the Joke Shop uses ID/ticket/wand lines, and the Studio uses ticket/credential/guest-list lines.
|
||||||
the GitHub cache service; import is fast). `GODOT_SILENCE_ROOT_WARNING=1` set on jobs.
|
- CI cache evaluation: Gitea Actions cache was tested for the Godot binary and
|
||||||
|
trimmed 4.7.2 templates. Restores were inconsistent and saves timed out in
|
||||||
|
`CreateCacheEntry` or were rejected as reserved by another job. The cache steps
|
||||||
|
are disabled until the host cache service is healthy; keep direct downloads.
|
||||||
|
`GODOT_SILENCE_ROOT_WARNING=1` is set on jobs.
|
||||||
Logs live in `.gitea/logs/` after each run.
|
Logs live in `.gitea/logs/` after each run.
|
||||||
- Visual polish (v0.2.0): phase lighting (GameManager.PHASE_LIGHTING + ProceduralSkyMaterial),
|
- Visual polish (v0.2.0): phase lighting (GameManager.PHASE_LIGHTING + ProceduralSkyMaterial),
|
||||||
street lamps + night mode in WorldBuilder.set_night_mode, marquee Label3D, crosswalk/planters/
|
street lamps + night mode in WorldBuilder.set_night_mode, marquee Label3D, crosswalk/planters/
|
||||||
@@ -631,13 +931,14 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
- Shift length: `GameManager` uses the scenario's `shift_duration_s` (900s ≈ 10-15 min);
|
- Shift length: `GameManager` uses the scenario's `shift_duration_s` (900s ≈ 10-15 min);
|
||||||
tests and the screenshot tool override elapsed time directly.
|
tests and the screenshot tool override elapsed time directly.
|
||||||
- Headless (dummy renderer) logs "Parameter m is null" from `mesh_get_surface_count`; it does not occur with a real GL driver and does not affect logic.
|
- Headless (dummy renderer) logs "Parameter m is null" from `mesh_get_surface_count`; it does not occur with a real GL driver and does not affect logic.
|
||||||
- Steps are visual-only (no collider) so NPCs can path to the entrance; the guard spawns on the landing so it never needs to climb.
|
- Steps have per-tread colliders (`_build_steps()` adds a `StaticBody3D` with one `BoxShape3D`
|
||||||
|
per tread), so the guard and NPCs walk up the entrance; the guard spawns on the landing.
|
||||||
- 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.
|
- Venue `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI.
|
||||||
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
|
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
|
||||||
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
|
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
|
||||||
- Headless suite is **229/229**; the windowed smokes, screenshot compare and judge pass. Remaining visual follow-ups are in `TODO.md`.
|
- Headless suite is **272/272**; Python tooling is **108/108**; screenshot compare is **21 good / 0 bad** and the automatic judge is **21/21 match, 0 diff**. 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
|
||||||
@@ -647,8 +948,9 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
- Do not snapshot right after changing a transform in the same frame: `capture_screenshots.gd`
|
- Do not snapshot right after changing a transform in the same frame: `capture_screenshots.gd`
|
||||||
needs a state/frame gap or the capture uses the old camera orientation.
|
needs a state/frame gap or the capture uses the old camera orientation.
|
||||||
- `EventScheduler` probabilities are per simulated second, not per call. Never poll it per frame.
|
- `EventScheduler` probabilities are per simulated second, not per call. Never poll it per frame.
|
||||||
- Screenshot staging lives in `src/test/capture_screenshots.gd` (guard at z≈10.5 facing the door).
|
- Screenshot staging lives in `src/test/capture_screenshots.gd` (guard at z≈10.5 facing the door)
|
||||||
`docs/screenshots/` copies are manual: copy from `user://screenshots/`.
|
and `src/test/capture_ui_states.gd` (UI states 14–21). `tools/check_screenshots.sh` renders all
|
||||||
|
captures into `docs/screenshots/`; do not regenerate them by hand.
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
@@ -659,16 +961,17 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
## Plan vs code
|
## Plan vs code
|
||||||
|
|
||||||
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
|
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
|
||||||
2026-11-01 the playable build covers all three venues, controls, access/radio/evidence/review,
|
2026-11-03 the playable build covers the three venues, tutorials/campaign/checkpoints, the 0.9.3
|
||||||
the Blender asset library, the realism pass, and the visual-alignment pass. `TODO.md` holds only
|
UI/accessibility pass, the 0.9.4 NPC visual optimization, the 0.9.5 texture/material pass, the
|
||||||
the realism follow-ups and optional future ideas. The project uses a custom headless runner, not
|
0.9.6 integration checkpoint, and the Blender + realism asset pipeline. `TODO.md` holds the remaining
|
||||||
GUT, and Gitea Actions, not GitHub Actions.
|
0.9.6–0.10.0 work. The project uses a custom headless runner, not GUT, and Gitea Actions, not
|
||||||
|
GitHub Actions.
|
||||||
|
|
||||||
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|
||||||
|
|
||||||
## Uncommitted Workstreams
|
## Uncommitted Workstreams
|
||||||
|
|
||||||
- **None (2026-11-01).** The NPC detail pass, the realism follow-ups and the flashlight beam
|
- **None (2026-11-03).** The 0.9.6 integration checkpoint is committed, tagged
|
||||||
decision are committed together with the portrait capture and the refreshed screenshots.
|
`v0.9.6` and published with seven assets; the workspace is clean apart from
|
||||||
Keep the rule from `AGENTS.md`: check `git status` before committing, and never revert
|
local/gitignored realism and capture output. Keep the rule from `AGENTS.md`: check `git status`
|
||||||
another contributor's in-flight files.
|
before committing, and never revert another contributor's in-flight files.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Premise
|
## Premise
|
||||||
|
|
||||||
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.
|
You are a private security guard working the entrance of a stylized reconstruction of the real El Capitan Entertainment Centre on Hollywood Boulevard. 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, access verification, and proportionate escalation**, not about attacking everyone who approaches.
|
The game is about **judgment, communication, access verification, and proportionate escalation**, not about attacking everyone who approaches.
|
||||||
|
|
||||||
@@ -14,6 +14,23 @@ The game is about **judgment, communication, access verification, and proportion
|
|||||||
- **Emergency help is a last resort.** Responders handle crises the guard cannot safely manage; calling them has costs.
|
- **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.
|
- **Readability beats raw fidelity.** The GL Compatibility renderer, clear silhouettes, the focus ring, and the HUD stay legible.
|
||||||
|
|
||||||
|
## Development Priorities
|
||||||
|
|
||||||
|
The active planning order is set in [`TODO.md`](TODO.md) and in `AGENTS.md`:
|
||||||
|
|
||||||
|
1. **Character development (unversioned).** Expand the owned procedural character library with
|
||||||
|
crowd, age, build, hair and outfit variety. Keep the frozen bone/tint/gear contract; see
|
||||||
|
[ADR-0015](docs/adr/0015-npc-model-variety-sourcing.md) and
|
||||||
|
[`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md). Quaternius is a reference;
|
||||||
|
MakeHuman is deferred to a separate local pipeline.
|
||||||
|
2. **Local map development (unversioned).** Move beyond stylized venue reconstruction with
|
||||||
|
licensed OpenStreetMap and 3D building data, downloaded reference imagery for photoreal
|
||||||
|
facades, AI-assisted composition, and a Gaussian-splatting evaluation that stays in the
|
||||||
|
development pipeline unless it proves runtime-safe.
|
||||||
|
3. **Versioned release hardening.** The 0.10.0 release candidate and later semantic versions come
|
||||||
|
after the unversioned development tracks in planning order. Versions are assigned only when a
|
||||||
|
track's scope is accepted into a release.
|
||||||
|
|
||||||
## Core Gameplay Loop
|
## Core Gameplay Loop
|
||||||
|
|
||||||
1. Watch the entrance, steps, queue, and sidewalk.
|
1. Watch the entrance, steps, queue, and sidewalk.
|
||||||
@@ -96,15 +113,19 @@ A poor shift involves excessive force, confronting innocent people, ignoring a r
|
|||||||
|
|
||||||
## Venues
|
## Venues
|
||||||
|
|
||||||
### Hollywood Boulevard Building
|
### El Capitan Entertainment Centre (6840 Hollywood Blvd)
|
||||||
|
|
||||||
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.
|
The original scenario: the historic Masonic Temple / El Capitan Entertainment Centre, with six Ionic columns, eight stone steps, the Hollywood Walk of Fame sidewalk, neighbouring theatre and retail frontage, and the four-lane boulevard. Tourists, performers, influencers, confused visitors, intoxicated pedestrians, trespassers, people in crisis, and rare violent threats approach from the street.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
### The Joke Shop
|
### 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.
|
A Sunset Strip comedy club inspired by The Comedy Store at 8433 Sunset Blvd: a
|
||||||
|
low black hall under a deep, signature-covered canopy, a circular blade sign,
|
||||||
|
red velvet curtains and the dry hillside rising behind it. The door person
|
||||||
|
checks IDs, tickets, reservations, guest-list status, and screens guests with a
|
||||||
|
handheld wand under venue policy.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -148,18 +169,19 @@ The action wheel prioritizes de-escalation: Stop, Look, Point, No, Ask, Inspect,
|
|||||||
|
|
||||||
The visual direction is readable stylized realism:
|
The visual direction is readable stylized realism:
|
||||||
|
|
||||||
- **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.
|
- **Character direction:** 8 skinned NPC variants, 18 gear pieces and 20 street/venue props, with four crowd-variety variants prototyped. The generator-first expansion is the active character track ([ADR-0015](docs/adr/0015-npc-model-variety-sourcing.md)); Quaternius is a reference and MakeHuman is deferred to a local pipeline.
|
||||||
- **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.
|
- **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 tight flashlight spot (no beam geometry), and denser foliage.
|
||||||
|
- **Local map direction:** the venues are currently stylized reconstructions. The local-map track evaluates licensed OpenStreetMap/3D building data, licensed reference photography, AI-assisted composition and Gaussian splatting as a development tool; online data and splat rendering are not runtime dependencies.
|
||||||
- **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.
|
- **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.
|
- **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.
|
||||||
|
|
||||||
Remaining visual work is tracked in `TODO.md` (NPC fidelity and the two venue stages), not in this plan.
|
The active planning order — unversioned character and local-map development first, then the versioned 0.10.0 release-candidate hardening and later releases — is tracked in [`TODO.md`](TODO.md); this plan stays at the product/design level.
|
||||||
|
|
||||||
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).
|
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), [ADR-0015](docs/adr/0015-npc-model-variety-sourcing.md).
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **Engine:** Godot 4.3, GL Compatibility renderer (Web parity).
|
- **Engine:** Godot 4.7.2, GL Compatibility renderer (Web parity); optional Forward+ Desktop HQ build.
|
||||||
- **Language:** GDScript.
|
- **Language:** GDScript.
|
||||||
- **Architecture:** venue-driven simulation; shared systems plus `Resource`-based scenario data.
|
- **Architecture:** venue-driven simulation; shared systems plus `Resource`-based scenario data.
|
||||||
- **Navigation:** `NavigationRegion3D` / `NavigationAgent3D`.
|
- **Navigation:** `NavigationRegion3D` / `NavigationAgent3D`.
|
||||||
@@ -172,7 +194,7 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD
|
|||||||
|
|
||||||
## Versioning and Delivery
|
## Versioning and Delivery
|
||||||
|
|
||||||
`config/version` in `project.godot` is the single version source (currently `0.7.2`). Releases are tagged `v<version>`; the CI pipeline builds and publishes the standard artifacts plus the optional Desktop HQ zip. Version bumps follow [Semantic Versioning](https://semver.org/). See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands.
|
`config/version` in `project.godot` is the single version source (currently `0.10.0-rc.1`). Releases are tagged `v<version>`; the CI pipeline builds and publishes the standard artifacts plus the optional Desktop HQ zip. Version bumps follow [Semantic Versioning](https://semver.org/): every major, minor and patch increment needs a changelog entry, a local `tools/build_releases.sh` build, and artifact/checksum verification before tagging. Unversioned development tracks do not receive a version until their scope enters a release. See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands.
|
||||||
|
|
||||||
## Design Direction
|
## Design Direction
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Privacy Note
|
||||||
|
|
||||||
|
Gig Line runs locally. The game itself does not include telemetry, analytics,
|
||||||
|
accounts, advertising, cloud saves or network requests. It does not access the
|
||||||
|
camera, microphone, contacts or location, and the body-camera feature is a
|
||||||
|
simulated overlay, not a real recording device.
|
||||||
|
|
||||||
|
Player data is written only to Godot's local `user://` directory:
|
||||||
|
|
||||||
|
- `settings.json`
|
||||||
|
- `campaign.json`
|
||||||
|
- `shift_results.json`
|
||||||
|
- `shift_state.json`
|
||||||
|
|
||||||
|
Those files stay on the player's machine unless the player copies them. Deleting
|
||||||
|
the files (or the `Gig Line` user directory) resets the profile. See
|
||||||
|
[`docs/getting-started.md`](docs/getting-started.md#save-data-and-migration) for
|
||||||
|
locations, migration and rollback instructions.
|
||||||
|
|
||||||
|
The repository's development tools can call OpenRouter when a developer supplies
|
||||||
|
`OPENROUTER_API_KEY`; those calls generate or judge development assets and are
|
||||||
|
not part of the shipped game. The key and generated review artifacts remain
|
||||||
|
local/gitignored.
|
||||||
|
|
||||||
|
Questions about a storefront release should be checked against the final package
|
||||||
|
and the provider terms recorded in [`CREDITS.md`](CREDITS.md); external legal and
|
||||||
|
storefront approval is still pending for 0.10.0.
|
||||||
@@ -6,20 +6,30 @@ The central design goal is simple:
|
|||||||
|
|
||||||
> 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.
|
||||||
|
|
||||||
|
## Development Priorities
|
||||||
|
|
||||||
|
The active planning order lives in [`TODO.md`](TODO.md) and [`AGENTS.md`](AGENTS.md):
|
||||||
|
|
||||||
|
1. **Character development** — crowd, age, build, hair and outfit variety on the owned procedural library, keeping the frozen NPC contract. Source evaluation and the licence decision are in [`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md).
|
||||||
|
2. **Local map development** — licensed OpenStreetMap/3D data, reference-derived photoreal facades, AI-assisted composition and a Gaussian-splatting development evaluation.
|
||||||
|
3. **Versioned release hardening** — the 0.10.0 release candidate and later semantic versions follow the unversioned development tracks.
|
||||||
|
|
||||||
|
Versions are assigned only when a track's scope is accepted into a release. Every major, minor and patch version requires a changelog entry, a local release build and artifact verification before tagging.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  | |
|
|  | |
|
||||||
|
|
||||||
*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,
|
*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.*
|
precision walk, the Joke Shop's Sunset Strip frontage, the TV Studio arrival, and the prop lineup.*
|
||||||
|
|
||||||
## ⬇️ Downloads
|
## ⬇️ Downloads
|
||||||
|
|
||||||
@@ -31,15 +41,21 @@ precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup.
|
|||||||
| **Linux — portable** | [`gig-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** | [`gig-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) |
|
| **Web (browser)** | [`gig-line-web-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unzip and serve the folder from any static host (no special headers) |
|
||||||
|
| **Desktop HQ** | [`gig-line-desktop-hq-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · optional Forward+ build with volumetric fog/SDFGI/flashlight; requires a Vulkan-capable GPU/driver |
|
||||||
|
|
||||||
**All releases:** <https://git.allucanget.biz/zwitschi/security-game/releases>
|
**All releases:** <https://git.allucanget.biz/zwitschi/security-game/releases>
|
||||||
|
|
||||||
> Downloads are public (no sign-in required) since the host's instance-wide
|
> Downloads are public (no sign-in required) since the host's instance-wide
|
||||||
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
|
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
|
||||||
>
|
>
|
||||||
> **Latest: v0.7.0** — distinct Joke Shop and TV Studio geometry, fitted NPC gear,
|
> **Latest published: v0.9.6** — the integration checkpoint: the 0.9.5 texture
|
||||||
> and the post-v0.6.0 realism/visual-alignment work (v2 skylines/textures, UI mockup
|
> and material refinement (256² NPC maps, a four-times fabric weave without baked
|
||||||
> art, flashlight beam, HUD fixes).
|
> AO blotches, repaired Walk of Fame/awning surfaces and per-phase skyline haze)
|
||||||
|
> is packaged with the v0.9.4 NPC visual optimization, the Hollywood/Joke Shop
|
||||||
|
> maps and the v0.9.3 UI/accessibility polish, with the full gallery, smoke matrix
|
||||||
|
> and Web/Desktop HQ build re-verified.
|
||||||
|
>
|
||||||
|
> **Current source: v0.10.0-rc.1** — the release-candidate source; the final tag is pending external playtest and legal review.
|
||||||
> 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
|
||||||
@@ -52,14 +68,15 @@ precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup.
|
|||||||
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
|
||||||
[Setup and Run](#setup-and-run) 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 + GDScript): a procedural Hollywood Boulevard entrance, a first-person guard, simulated NPCs with intents and reactions, the six-level escalation model, access inspection and admission decisions, radio calls with credibility checks, reputation, crowd mood, and an end-of-shift review.
|
The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4 + GDScript): a stylized El Capitan Entertainment Centre block, 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: watch the entrance and sidewalk, judge why
|
A typical shift lasts 10–15 minutes: watch the entrance and sidewalk, judge why
|
||||||
each person is approaching, verify access with recognition, wristbands, IDs,
|
each person is approaching, verify access with recognition, wristbands, IDs,
|
||||||
tickets or credentials, and escalate only when necessary. The game rewards calm
|
tickets or credentials, and escalate only when necessary. The game rewards calm
|
||||||
resolution over force. The full mechanics — the three venues, the six-level
|
resolution over force. The venue menu splits Hollywood Boulevard into Day and
|
||||||
|
Night rows; the full mechanics — the three venues, the six-level
|
||||||
escalation model, and the core systems — are in
|
escalation model, and the core systems — are in
|
||||||
[`docs/gameplay.md`](docs/gameplay.md); the design rationale lives in
|
[`docs/gameplay.md`](docs/gameplay.md); the design rationale lives in
|
||||||
[`PLAN.md`](PLAN.md).
|
[`PLAN.md`](PLAN.md).
|
||||||
@@ -74,7 +91,7 @@ decisions are recorded as ADRs in [`docs/adr/`](docs/adr/).
|
|||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **Engine:** Godot 4.x · **Language:** GDScript
|
- **Engine:** Godot 4.7.2 · **Language:** GDScript
|
||||||
- **NPCs:** Blender-authored skinned library (`assets/models/blender/`) with procedural fallbacks; lightweight FSMs on `NpcProfile`
|
- **NPCs:** Blender-authored skinned library (`assets/models/blender/`) with procedural fallbacks; lightweight FSMs on `NpcProfile`
|
||||||
- **World:** procedural `WorldBuilder` + generated prop models
|
- **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/`
|
- **Art:** v2 skyline/surface textures in `assets/textures/ai/game_ready/v2/picks/`; UI/scene art in `assets/images/`
|
||||||
@@ -100,30 +117,57 @@ Full setup, every run/test/smoke command, and the controls table are in
|
|||||||
|
|
||||||
## Project Documentation
|
## Project Documentation
|
||||||
|
|
||||||
- [`PLAN.md`](PLAN.md) — Game design plan and scenario details.
|
- [`PLAN.md`](PLAN.md) — Game design plan, development priorities and scenario details.
|
||||||
- [`AGENTS.md`](AGENTS.md) — Development and collaboration workflow.
|
- [`TODO.md`](TODO.md) — Priority-ordered tasks: character and local-map development, release milestones, and future work.
|
||||||
- [`TODO.md`](TODO.md) — Active checkbox task list.
|
- [`docs/README.md`](docs/README.md) — Documentation index, current priorities and visual previews.
|
||||||
|
- [`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md) — Character-source evaluation, licence rules and the character-development plan.
|
||||||
|
- [`AGENTS.md`](AGENTS.md) — Development, commit, build and collaboration workflow.
|
||||||
- [`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.
|
- [`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/gameplay.md`](docs/gameplay.md) — Gameplay systems, venues, and the escalation model.
|
||||||
|
- [`docs/hollywood-boulevard-map.md`](docs/hollywood-boulevard-map.md) — Real-site references, game mapping and limitations for the Hollywood venue.
|
||||||
|
- [`docs/joke-shop-map.md`](docs/joke-shop-map.md) — Real-site references, game mapping and limitations for the Joke Shop / Sunset Strip venue.
|
||||||
|
- [`docs/tutorial.md`](docs/tutorial.md) — First-shift tutorial beats and rules.
|
||||||
- [`docs/asset-tooling.md`](docs/asset-tooling.md) — Asset pipelines, model tools, and screenshot gates.
|
- [`docs/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/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/development-status.md`](docs/development-status.md) — Implemented features and what's next.
|
||||||
|
- [`docs/accessibility.md`](docs/accessibility.md) — Accessibility features, known gaps and statement.
|
||||||
|
- [`docs/known-limitations.md`](docs/known-limitations.md) — Release-candidate blockers and accepted limitations.
|
||||||
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md) — Manual smoke-test checklist.
|
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md) — Manual smoke-test checklist.
|
||||||
|
- [`CREDITS.md`](CREDITS.md) — Credits, AI/reference provenance and licensing notes.
|
||||||
|
- [`PRIVACY.md`](PRIVACY.md) — Local-only data, save files and development API use.
|
||||||
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
|
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
|
||||||
|
- [`docs/npc-visual-review.md`](docs/npc-visual-review.md) — 0.9.4 NPC baseline, acceptance criteria and ranked visual gaps; 0.9.5 texture comparison is in [`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`](docs/blender_previews/sheet_texture_refinement_0.9.5.jpg).
|
||||||
|
- [`docs/npc-proportion-audit.md`](docs/npc-proportion-audit.md) — NPC proportion measurements against the shared stylized guide.
|
||||||
- [`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
|
||||||
|
|
||||||
v0.7.0 shipped distinct venue geometry: the Joke Shop alley and TV Studio
|
v0.10.0-rc.1 is the current release-candidate source: the 0.9.6 integration
|
||||||
entrance no longer share the Hollywood layout. It builds on v0.6.1's fitted NPC
|
checkpoint plus save-migration hardening and release tooling, on top of the 0.9.5
|
||||||
gear and v0.6.0's Blender asset library, baked AO and environment lift. See
|
texture refinement (256² NPC maps, a 4× fabric weave with baked-AO removal, repaired
|
||||||
[`docs/development-status.md`](docs/development-status.md) for the full
|
Walk of Fame/awning surfaces and per-phase skyline haze), on top of the v0.9.4
|
||||||
implemented list and [`TODO.md`](TODO.md) for what's next. Manual verification
|
NPC visual optimization and the v0.9.3 UI/accessibility polish pass. It builds on
|
||||||
uses the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
|
v0.9.2's Hollywood/Joke Shop map rebuilds, v0.9.1's Godot 4.7.2 baseline,
|
||||||
|
v0.9.0's production-quality pass, v0.8's
|
||||||
|
tutorial/campaign/resume work and the v0.7.x venue/gear/judge work. The active
|
||||||
|
planning priorities are the unversioned character and local-map development tracks;
|
||||||
|
the 0.10.0 release candidate is the next versioned release. See
|
||||||
|
[`docs/development-status.md`](docs/development-status.md) for the implemented list
|
||||||
|
and [`TODO.md`](TODO.md) for the priority-ordered roadmap. Manual verification uses
|
||||||
|
the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
|
||||||
|
|
||||||
|
## Credits, Privacy and Accessibility
|
||||||
|
|
||||||
|
Gig Line is a local-only single-player game: there is no runtime telemetry or
|
||||||
|
network service, and saves stay in Godot's local `user://` folder. The
|
||||||
|
body-camera overlay is simulated. Asset provenance, AI-assisted image usage and
|
||||||
|
credits are recorded in [`CREDITS.md`](CREDITS.md); the local-data policy is in
|
||||||
|
[`PRIVACY.md`](PRIVACY.md); accessibility features and gaps are in
|
||||||
|
[`docs/accessibility.md`](docs/accessibility.md); and release-candidate blockers
|
||||||
|
are listed in [`docs/known-limitations.md`](docs/known-limitations.md).
|
||||||
|
|
||||||
## Development Workflow
|
## Development Workflow
|
||||||
|
|
||||||
|
|||||||
@@ -3,23 +3,623 @@
|
|||||||
Work is grouped by theme. Each item should be small enough to complete and verify on its own;
|
Work is grouped by theme. Each item should be small enough to complete and verify on its own;
|
||||||
see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these plans.
|
see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these plans.
|
||||||
|
|
||||||
## Realism follow-ups (deferred)
|
**Priority order:** 3D model re-evaluation → character development → local map development →
|
||||||
|
versioned release milestones (0.10.0) → future/deferred work. See `AGENTS.md` §Priority and
|
||||||
|
Versioning and §Local Blender skills.
|
||||||
|
|
||||||
- [x] Re-run the screen-design judge for `12-joke-shop`, `13-studio-arrival` and the
|
The MIT Blender skill pack lives locally at `.agents/skills/blender-skills/` (gitignored). Every
|
||||||
Forward+ A/B with the valid key: **13/13 match, 0 diff**; the HQ A/B is in the report.
|
3D workstream below names the skills to load; they are guidance only, and the project contract,
|
||||||
- [ ] Capture a hooded/winter NPC close-up once a scenario spawns types 5-7 (the explicit
|
`blender_assets.py audit`/`fit`/`inspect` and the capture gates stay authoritative.
|
||||||
`--portrait-type` override is already in `capture_realism.gd`).
|
|
||||||
- [ ] Human-authored tree/hedge silhouettes: the whole scene now reads well except the foliage,
|
|
||||||
which is still flat-shaded clumps (`_shrub()`/`_tree()` in `tools/blender_assets.py`).
|
|
||||||
Only worth doing if a higher-poly organic builder is added to the library.
|
|
||||||
- [ ] Camera height: the concept frames are shot at ~1.55 m with a slightly wider lens; the game
|
|
||||||
camera sits at 1.7 m. Decide whether the first-person height should match the art direction
|
|
||||||
or stay at the current gameplay value.
|
|
||||||
|
|
||||||
## Future ideas (optional, not scheduled)
|
## 3D model re-evaluation
|
||||||
|
|
||||||
- [ ] Live 3D body-camera replay (the timeline is text-only today).
|
Goal: re-judge every shipped 3D asset against the newly available Blender skill guidance, rank
|
||||||
- [ ] Additional venues or seasonal events authored from the new `.tres` files.
|
the real gaps, and fix only what the rankings justify. This is an unversioned development track:
|
||||||
- [ ] Real voice/dialogue audio (the crowd bed and radio crackle are procedural).
|
it must not change the `npc_base` contract, the renderer or the 0.10.0 release candidate.
|
||||||
|
|
||||||
|
Scope: 8 library characters (`npc_casual`, `npc_jacket`, `npc_coat`, `npc_vest`, `npc_dress`,
|
||||||
|
`npc_hoodie`, `npc_firefighter`, `npc_guard`), the frozen `npc_base` fallback, 18 gear pieces,
|
||||||
|
20 library props, 6 generated props, and the three venue families.
|
||||||
|
|
||||||
|
**Exit:** `docs/3d-model-review.md` scores every catalogue asset; every P0/P1 is fixed or
|
||||||
|
explicitly accepted with a measurement; `inspect`/`audit`/`fit`/proportions, the 24-NPC budget
|
||||||
|
and the capture gates are green; no runtime contract, dependency or release artifact changed
|
||||||
|
except the rebuilt assets themselves.
|
||||||
|
|
||||||
|
### A. Baseline, scorecard and routing
|
||||||
|
|
||||||
|
- [x] Load `blender-director` plus the per-family specialists before touching geometry, and
|
||||||
|
record the skill stack and the advice that changed a decision in `docs/3d-model-review.md`.
|
||||||
|
(Routing, QA severity/verdict, texel-density tiers, character loop criteria and budget rules
|
||||||
|
recorded in the review doc; MCP-only steps translated to headless flatpak runs.)
|
||||||
|
- [x] Re-run the neutral NPC baseline (48 frames), the final-context review (96 frames), the
|
||||||
|
prop gallery and the 24-NPC budget on the current build; keep raw frames in `tmp/` and do
|
||||||
|
not replace committed sheets until a fix lands. (48/48 and 96/96 frames captured under
|
||||||
|
`xvfb-run`, none blank; raw sets in `tmp/model-baseline` and `tmp/model-review`; committed
|
||||||
|
sheets untouched. 24-NPC smoke still 6.94 ms / 168 meshes / 131,616 triangles.)
|
||||||
|
- [x] Create `docs/3d-model-review.md` with a per-asset scorecard: silhouette, anatomy/proportion,
|
||||||
|
topology/edge flow, UV/texel density, textures/materials, rig/weights, LOD/impostor,
|
||||||
|
collision/grounding, export contract, provenance, skill route, verdict (P0/P1/P2/accept).
|
||||||
|
(Doc created with character/gear/prop/venue tables, verdict columns and a ranked candidate
|
||||||
|
list; unmeasured cells are marked `—` instead of being guessed.)
|
||||||
|
- [x] Collect the baseline measurements in one table: `blender_assets.py inspect`, `audit`, `fit`,
|
||||||
|
`npc_proportions.py`, texel density, triangles/meshes, 24-NPC frame time. (All seven captured;
|
||||||
|
`texels` and `inspect --json --out` were added to make the measurements repeatable. All 46
|
||||||
|
assets audit/inspect clean; texel density 64–122 px/m authored, 287–440 effective fabric.)
|
||||||
|
- [ ] Review the scorecard with the project owner, rank the P0/P1 gaps and decide fix vs accept
|
||||||
|
per item; record the decision and the reason in the review document.
|
||||||
|
|
||||||
|
### B. Character re-evaluation (8 variants + fallback)
|
||||||
|
|
||||||
|
*Skill route: `character-artist`, `retopology`, `rigging`, `uv-workflow`, `texture-workflow`,
|
||||||
|
`materials`, `hair-groom`, `asset-optimization`, `lookdev`, `qa-review`.*
|
||||||
|
|
||||||
|
- [ ] Face/head: review eye/mouth loops, cheek/brow/chin transitions and faceting at the 2 m
|
||||||
|
portrait distance against `character-artist` and `retopology`; write the concrete edits.
|
||||||
|
- [ ] Hands/grip: re-check palm, finger segments, thumb and gear contact; decide between a
|
||||||
|
finger-curl rig or the current open hand plus `HAND_ITEM_FORWARD`, and record the limit.
|
||||||
|
- [ ] Weights/pose: capture idle, walk, sit and wave; inspect shoulder, hip and neck collapse;
|
||||||
|
fix the worst offenders using `rigging` practice without changing the 13-bone contract.
|
||||||
|
- [ ] UV/texel density: measure the 256² slot maps; decide whether atlas, UDIM or a resolution
|
||||||
|
change is justified. Do not change `CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` without re-running the
|
||||||
|
budget smoke and the Web/Compatibility path.
|
||||||
|
- [ ] Cloth and hair: re-check coat/dress hem clipping and the hair/beard/headgear meshes against
|
||||||
|
`hair-groom` guidance; decide explicitly whether cloth simulation stays out of scope.
|
||||||
|
- [ ] Fix only the ranked P0/P1s, one asset family per commit; refresh the comparison sheet and
|
||||||
|
re-run `inspect`, `audit`, `fit` and `npc_proportions.py` after each fix.
|
||||||
|
- [ ] `qa-review` sign-off: a ship/no-ship verdict per variant with accepted limits recorded.
|
||||||
|
|
||||||
|
### C. Gear and prop re-evaluation
|
||||||
|
|
||||||
|
*Skill route: `prop-artist`, `hard-surface`, `vegetation-artist`, `materials`, `collision-proxy`,
|
||||||
|
`lod-pipeline`, `qa-review`.*
|
||||||
|
|
||||||
|
- [ ] Gear: review the 18 pieces for silhouette, straps/buckles, helmet and lanyard readability;
|
||||||
|
verify attach space and body clearance with `fit`; rebuild only what fails the scorecard.
|
||||||
|
- [ ] Props: review the 20 library props plus the 6 generated props for bevels, chamfers,
|
||||||
|
silhouette, ground contact and shared material language (`prop-artist`, `hard-surface`);
|
||||||
|
list the rebuilds that are actually worth the triangle budget.
|
||||||
|
- [ ] Foliage: replace the flat-shaded `_shrub()`/`_tree()` clumps with `vegetation-artist`-style
|
||||||
|
carded clusters; measure triangles and keep the 60k `LIBRARY_TRIANGLE_BUDGET`.
|
||||||
|
- [ ] Materials and emissives: `lookdev` pass on bulbs, signs, lanterns, the marquee and the
|
||||||
|
sedan at day and late; confirm the flashlight stays a tight spot with no beam geometry.
|
||||||
|
- [ ] Collision: verify every prop collider and navigation obstacle matches its silhouette and
|
||||||
|
add the missing `collision-proxy` shapes where walking is blocked.
|
||||||
|
- [ ] Exit: a prop/gear capture sheet, budgets and collision checks pass with no unreviewed asset.
|
||||||
|
|
||||||
|
### D. Venue and camera re-evaluation
|
||||||
|
|
||||||
|
*Skill route: `environment-artist`, `archviz`, `set-dressing`, `lighting`, `camera-cinematography`,
|
||||||
|
`lookdev`, `rendering`, `qa-review`.*
|
||||||
|
|
||||||
|
- [ ] Facade/venue review: rank the top five shape-language fixes for Hollywood, the Joke Shop and
|
||||||
|
the TV Studio (`environment-artist`, `archviz`) and check them against the map-budget tests.
|
||||||
|
- [ ] Lighting: `lighting`/`lookdev` pass on sconces, canopy, marquee and street lamps with
|
||||||
|
day/late captures; verify local attenuation stays at 1.0 and no constant-falloff light returns.
|
||||||
|
- [ ] Set dressing: review entrance and sidewalk prop clusters (`set-dressing`) for occlusion,
|
||||||
|
navigation obstacles and readable storytelling clusters.
|
||||||
|
- [ ] Camera height: decide 1.7 m gameplay framing vs the ~1.55 m concept frames using
|
||||||
|
`camera-cinematography`, and record the decision instead of leaving the mismatch open.
|
||||||
|
- [ ] Hooded/winter portrait: close the deferred close-up by staging a types 5–7 capture
|
||||||
|
(`--portrait-type`) or adding a scenario spawn that provides one.
|
||||||
|
|
||||||
|
### E. Gates and documentation
|
||||||
|
|
||||||
|
- [ ] Add the measurable checks the review needs (texel density, weight/pose sanity, collider
|
||||||
|
coverage, LOD/impostor check) as stdlib or GDScript gates where possible; CI stays offline.
|
||||||
|
- [ ] Add a coverage test so every catalogue asset has a scorecard row and verdict.
|
||||||
|
- [ ] Update `docs/blender-assets.md`, `docs/npc-visual-review.md`, `docs/3d-model-review.md`,
|
||||||
|
`docs/architecture.md` and `AGENTS.md` with the skill routes and the new gates.
|
||||||
|
- [ ] Record accepted limits and deferred rebuilds in `docs/known-limitations.md` and `MEMORY.md`.
|
||||||
|
|
||||||
|
## Character development
|
||||||
|
|
||||||
|
Goal: break the eight-character clone effect with four licence-cleared crowd-variety looks while
|
||||||
|
keeping the frozen `npc_base` bone, tint-slot and gear-attach contract. The procedural generator is
|
||||||
|
the primary path; Quaternius is a reviewed reference and MakeHuman is deferred to a separate local
|
||||||
|
development pipeline.
|
||||||
|
|
||||||
|
The roster build is deliberately sequenced **after** the 3D model re-evaluation: the new variants
|
||||||
|
must inherit the reviewed face/hand/gear baseline instead of multiplying whatever it finds.
|
||||||
|
|
||||||
|
*Skill route: `character-artist`, `retopology`, `rigging`, `uv-workflow`, `materials`,
|
||||||
|
`asset-optimization`, `lowpoly-style`, `godot-export`, `qa-review`.*
|
||||||
|
|
||||||
|
### Source review and decision
|
||||||
|
|
||||||
|
- [x] Research free/open-source character sources and evaluate at least three downloads against
|
||||||
|
the existing Blender contract. (Measured Quaternius CC0, Kenney CC0, Khronos
|
||||||
|
CesiumMan/RiggedFigure CC-BY and the MakeHuman CC0 base mesh; none is a drop-in.
|
||||||
|
Results, local preview sheet and SHA-256 provenance are in
|
||||||
|
`docs/npc-model-sourcing.md`.)
|
||||||
|
- [x] Review the sourcing research with the project owner and choose Option A (procedural),
|
||||||
|
Option B (Quaternius CC0 intake) or Option C (MakeHuman/MPFB body pipeline); record the
|
||||||
|
decision in an ADR and in `MEMORY.md`. (2026-11-03: extend the procedural generator as the
|
||||||
|
primary path, keep Quaternius as a reviewed prototype/reference, defer MakeHuman to a
|
||||||
|
separate local pipeline. Recorded in ADR-0015 and `MEMORY.md`.)
|
||||||
|
- [x] Add a source licence allow-list and provenance rule: CC0 preferred, CC-BY only with
|
||||||
|
attribution in `CREDITS.md`, and no service assets whose terms restrict redistribution.
|
||||||
|
(ADR-0015; the CC-BY research previews are attributed in `CREDITS.md`.)
|
||||||
|
- [x] Decide whether the external preview sheet stays local under `tmp/` or is committed with
|
||||||
|
the required CC-BY attribution. (Committed as
|
||||||
|
`docs/blender_previews/sheet_npc_sources_research.jpg` with Cesium CC-BY attribution;
|
||||||
|
the project-owned crowd prototype sheet is also committed.)
|
||||||
|
|
||||||
|
### Intake and validation
|
||||||
|
|
||||||
|
- [x] Add a Blender `inspect` path that imports a GLB, applies node transforms, reports
|
||||||
|
tris/verts, bounds, bones, materials, UVs and skinning, and lists contract failures.
|
||||||
|
(`blender_assets.py inspect --file ...` applies parent/object transforms, converts to
|
||||||
|
Godot Y-up, ignores the importer's bone-shape helper and prints budgets, bones, slots,
|
||||||
|
UVs, skinning and animations with issues/warnings; `--json` and `--category` supported.
|
||||||
|
Seven synthetic contract tests added; all 46 library GLBs inspect clean. `audit` remains
|
||||||
|
the final gate.)
|
||||||
|
- [ ] Add an `adapt` path for future external assets: scale/ground, weld seams, decimate, map the
|
||||||
|
five tint slots, and either rebuild the contract armature or record an explicit second-rig
|
||||||
|
contract. Use `retopology`/`rigging`/`uv-workflow` guidance for the manual steps, but keep
|
||||||
|
the Quaternius no-go: a contract rebuild needs a proportion warp, so this is not on the
|
||||||
|
character critical path.
|
||||||
|
- [ ] Keep `tools/blender_assets.py audit` as the single final gate; add a regression test for the
|
||||||
|
intake report/adaptation using a deterministic synthetic or licence-cleared fixture.
|
||||||
|
- [ ] Document the adapter commands and the go/no-go checks in `docs/asset-tooling.md`.
|
||||||
|
|
||||||
|
### Quaternius prototype
|
||||||
|
|
||||||
|
- [x] Prototype one Quaternius CC0 variant: weld/decimate to budget and capture Idle/Walk
|
||||||
|
renders. (12,518 imported verts -> 3,199 after weld -> 2,423 / 4,654 tris after a 0.75
|
||||||
|
decimate; Idle/Walk quality is good. Preview in
|
||||||
|
`docs/blender_previews/sheet_npc_sources_research.jpg`.)
|
||||||
|
- [x] Measure the prototype against the `npc_casual` controls: silhouette, rest pose, body
|
||||||
|
proportions and gear fit. (No-go for a contract rebuild: T-pose 62-bone realistic rig,
|
||||||
|
~0.96 m hip, ~1.68 m arm span and ~0.153 m shoulder joint do not match the arm-down
|
||||||
|
13-bone contract. Keep as reference; a second rig/animation path needs its own ADR.)
|
||||||
|
- [x] Make a go/no-go decision on Option B. (No-go for the current pass; proceed with the procedural
|
||||||
|
generator and keep Quaternius as a visual/style reference.)
|
||||||
|
|
||||||
|
### Roster and runtime
|
||||||
|
|
||||||
|
- [x] Approve four crowd-variety looks (build, head silhouette, hair/beard, outer layer and
|
||||||
|
accessory; no clothing that labels the NPC's behaviour). (`npc_crowd_a` bun/slim/sunglasses,
|
||||||
|
`npc_crowd_b` bald/broad/beard/shoulder bag, `npc_crowd_c` coat/umbrella, `npc_crowd_d`
|
||||||
|
hood/cup; prototype sheet committed.)
|
||||||
|
- [ ] Re-derive the four approved specs against the re-evaluated character baseline (face, hands,
|
||||||
|
proportions, gear offsets) so the new variants do not reintroduce the gaps the review found.
|
||||||
|
- [ ] Add the four approved specs to `CHARACTERS`, build and commit their GLBs/AO maps, and
|
||||||
|
update the manifest with roles and tint data. Build only the new assets, never a no-op full
|
||||||
|
rebuild, and remember that `export_filter="all_resources"` packs committed GLBs into the
|
||||||
|
next release build: keep this off `main` until after the 0.10.0 tag or accept the size.
|
||||||
|
- [ ] Replace the fixed `character_model_for()` mapping with deterministic per-role pools and add
|
||||||
|
tests for repeatability and duplicate avoidance (`godot-export` contract check included).
|
||||||
|
- [ ] Rebuild and verify gear fit for every new variant (`blender_assets.py fit` plus the runtime
|
||||||
|
gear checks) and re-run `inspect` on the four new GLBs.
|
||||||
|
|
||||||
|
### QA and release
|
||||||
|
|
||||||
|
- [ ] Run the 24-NPC budget smoke, portrait baseline, gear fit, headless suite and a release build
|
||||||
|
with the expanded library.
|
||||||
|
- [ ] Update `CREDITS.md`, `docs/development-status.md`, `docs/npc-visual-review.md` and
|
||||||
|
`CHANGELOG.md`; record any accepted visual limits.
|
||||||
|
- [ ] Exit: a reviewed 12-character library passes contract audit, portrait/gear acceptance,
|
||||||
|
the 24-NPC budget and licence/provenance review without changing the runtime contract.
|
||||||
|
|
||||||
|
### Deferred MakeHuman local pipeline
|
||||||
|
|
||||||
|
- [x] Evaluate the MakeHuman CC0 base mesh for a later separate local development pipeline.
|
||||||
|
(19,158 verts / 36,972 tris; scaled to 1.82 m; decimate 0.35 -> 3,780 verts / 6,468 tris;
|
||||||
|
UVs present, no normals/material groups/rig. Feasible only with retopology, a custom rig and
|
||||||
|
project-owned clothing; keep out of CI/build.)
|
||||||
|
- [ ] Decide whether body-shape diversity still matters after the 12-character library lands; if
|
||||||
|
yes, prototype the local MPFB/MakeHuman pipeline outside the repo and commit only the final
|
||||||
|
CC0-derived assets with provenance.
|
||||||
|
|
||||||
|
## Local map development
|
||||||
|
|
||||||
|
Goal: build a local, data-backed map pipeline that can replace or supplement the stylized venue
|
||||||
|
geometry with licensed OpenStreetMap footprints, 3D building massing, reference-derived photoreal
|
||||||
|
facades and AI-assisted composition. This is an unversioned development track, not a release gate.
|
||||||
|
|
||||||
|
*Skill route: `environment-artist`, `archviz`, `hard-surface`, `geometry-nodes`,
|
||||||
|
`procedural-modeling`, `vegetation-artist`, `set-dressing`, `lighting`, `materials`,
|
||||||
|
`texture-workflow`, `lookdev`, `scene-assembly`, `qa-review`.*
|
||||||
|
|
||||||
|
### Data, licensing and scope
|
||||||
|
|
||||||
|
- [ ] Define the first target block and whether the result replaces, supplements or only informs
|
||||||
|
the current stylized venue specs (`docs/hollywood-boulevard-map.md`,
|
||||||
|
`docs/joke-shop-map.md`).
|
||||||
|
- [ ] Audit OpenStreetMap's ODbL attribution/share-alike obligations and decide whether derived
|
||||||
|
geometry ships, stays development-only, or needs a separate data layer.
|
||||||
|
- [ ] Choose an offline OSM extract workflow (Overpass/Geofabrik) and pin the extract date and
|
||||||
|
query so the map build is reproducible.
|
||||||
|
- [ ] Evaluate licence-compatible 3D building datasets (OSM Buildings, Overture, CityGML/LOD1–2
|
||||||
|
or local authority data); record coverage, accuracy, format and redistribution terms.
|
||||||
|
- [ ] Add a provenance sidecar for every map source and derived asset, including source URL,
|
||||||
|
licence, capture/extract date and attribution text.
|
||||||
|
|
||||||
|
### Reference imagery
|
||||||
|
|
||||||
|
- [ ] Write a reference shot list per block: facade materials, windows, signage, rooflines,
|
||||||
|
street furniture, curb cuts and lighting.
|
||||||
|
- [ ] Download and catalogue reference images from Wikimedia Commons, local/own photography or
|
||||||
|
other clearly licensed sources; record author, licence and attribution. Do not use
|
||||||
|
unlicensed Street View or scraped commercial imagery.
|
||||||
|
- [ ] Separate development-only references from shipped textures; commit only images with clear
|
||||||
|
redistribution rights and keep the rest under `tmp/` or the ignored reference folders.
|
||||||
|
- [ ] Define the photoreal target and a material-derivation workflow (albedo/normal/roughness,
|
||||||
|
AO, weathering) that matches the existing Compatibility renderer (`texture-workflow`,
|
||||||
|
`materials`, `lookdev`).
|
||||||
|
|
||||||
|
### Geometry and AI-assisted composing
|
||||||
|
|
||||||
|
- [ ] Prototype OSM footprint import into Blender at game scale and orientation, then compare it
|
||||||
|
against the current venue footprint/collider layout.
|
||||||
|
- [ ] Import 3D building massing for the target block, clean the topology and assign LOD bands;
|
||||||
|
do not import unbounded city-scale meshes into the runtime (`scene-assembly`,
|
||||||
|
`asset-optimization`, `lod-pipeline`).
|
||||||
|
- [ ] Build a reusable photoreal facade kit (window bays, cornices, storefronts, awnings, signage)
|
||||||
|
with `environment-artist`/`hard-surface`/`geometry-nodes` so buildings share materials and
|
||||||
|
stay inside the venue triangle/material budgets.
|
||||||
|
- [ ] Add an AI-assisted composition step for palette, facade rhythm and reference boards; record
|
||||||
|
the model, prompt and output, and keep human review as the acceptance gate. AI output must
|
||||||
|
not be treated as licensed map data or shipped without provenance.
|
||||||
|
- [ ] Integrate the result behind `WorldBuilder.VENUE_SPECS` with colliders, navigation obstacles,
|
||||||
|
occlusion and the existing day/night materials.
|
||||||
|
|
||||||
|
### Gaussian splatting evaluation: photo intake and provenance
|
||||||
|
|
||||||
|
Gaussian splatting is evaluated as a photo-driven pipeline: licensed or permissioned photos
|
||||||
|
become a reference, texture source, mesh proxy or distant backdrop. It is an unversioned local
|
||||||
|
track with no runtime dependency or release gate until the ADR closes it, and it extends the
|
||||||
|
reference-image licence and privacy gates above rather than replacing them.
|
||||||
|
|
||||||
|
- [ ] Define the accepted photo channels: project/team captures, volunteer or user-supplied
|
||||||
|
photos with written permission, and downloaded images from clearly licensed archives
|
||||||
|
(Wikimedia Commons, Openverse, Mapillary and similar). Record which channel produced
|
||||||
|
each reconstruction.
|
||||||
|
- [ ] Write a one-page capture brief for user-supplied photos: overlapping arc or orbit around
|
||||||
|
the facade, 60–80% view overlap, 12 MP+ originals, fixed exposure/white balance, HDR and
|
||||||
|
filters off, no motion blur, no people or plates in frame where avoidable, overcast or
|
||||||
|
even light, and the facade height/doors/steps included in several frames.
|
||||||
|
- [ ] Build an intake checklist and provenance sidecar per photo set: contributor, consent or
|
||||||
|
property permission, licence and attribution text, source URL, capture date, camera/lens,
|
||||||
|
location, faces/plates/interiors present, redactions applied, file hashes and whether the
|
||||||
|
set is shipped or development-only.
|
||||||
|
- [ ] Set the licence gate: own photos need an explicit contributor licence; CC0/CC BY are
|
||||||
|
preferred; CC BY-SA triggers a share-alike review for derived textures and meshes; reject
|
||||||
|
Street View, social-media scrapes, unlicensed commercial imagery and AI-upscaled copies of
|
||||||
|
unknown originals. Attribution must be feasible in `CREDITS.md` before use.
|
||||||
|
- [ ] Set the privacy gate: blur or remove faces, minors, licence plates, screens, private
|
||||||
|
interiors and anything behind a closed door; obtain property permission where required;
|
||||||
|
keep a redacted copy as the reconstruction input and never commit the originals.
|
||||||
|
- [ ] Treat downloaded photo sets as sparse and of unknown intrinsics. Use them for reference,
|
||||||
|
palette and texture study, or as extra training views; do not expect a usable splat from
|
||||||
|
10–30 casual web photos of a facade.
|
||||||
|
- [ ] Keep photos, masks, COLMAP databases, checkpoints and splats outside git; commit only
|
||||||
|
derived, licensed, provenance-stamped assets that pass the review gate.
|
||||||
|
|
||||||
|
### Gaussian splatting: reconstruction and alignment
|
||||||
|
|
||||||
|
- [ ] Pin a reproducible offline toolchain (for example COLMAP or GLOMAP for camera poses plus
|
||||||
|
gsplat, OpenSplat, Nerfstudio or Brush for training), with exact versions, GPU/VRAM/disk
|
||||||
|
needs and known failure modes recorded in the sidecar.
|
||||||
|
- [ ] Reconstruct two quality tiers: a dense 100–400-photo arc/orbit capture and a sparse
|
||||||
|
10–30-photo downloaded set. Record camera-registration rate, training time, splat count,
|
||||||
|
file size and held-out PSNR/SSIM/LPIPS for both.
|
||||||
|
- [ ] Mask sky, people, vehicles and moving foliage before training; verify the masks do not
|
||||||
|
erase facade edges, signage or window detail, and keep the redacted input variant.
|
||||||
|
- [ ] Align the splat to game coordinates from the OSM footprint plus surveyed points (door
|
||||||
|
width, column spacing, step rise, storefront height) and record alignment residuals in a
|
||||||
|
Blender scene containing both the splat and the current venue geometry (`scene-assembly`).
|
||||||
|
- [ ] Judge quality from held-out views and a render from the game's day/night camera poses
|
||||||
|
(`camera-cinematography`, `lookdev`, `qa-review`), not from training views alone; reject
|
||||||
|
splats that only look correct from the capture path.
|
||||||
|
- [ ] Maintain a coverage map per block: which facades, corners and rooflines have usable views,
|
||||||
|
and which areas must fall back to the facade kit or stylized geometry.
|
||||||
|
|
||||||
|
### Gaussian splatting: map enhancement paths
|
||||||
|
|
||||||
|
- [ ] **Path A — Reference and QA (lowest risk):** render the splat from game camera poses and
|
||||||
|
compare it with the realism captures; derive dimensions, signage placement, facade rhythm
|
||||||
|
and material colours. No splat data reaches the runtime.
|
||||||
|
- [ ] **Path B — Texture derivation:** project splat renders onto the facade kit to produce
|
||||||
|
albedo, normal, roughness and AO candidates; de-light and bake them (`texture-workflow`),
|
||||||
|
then verify UV scale, tiling and readability under the Compatibility renderer.
|
||||||
|
- [ ] **Path C — Mesh proxy:** extract a mesh from the splat or dense reconstruction, retopo and
|
||||||
|
dress it in Blender (`retopology`, `prop-artist`), assign LODs (`lod-pipeline`), and keep it
|
||||||
|
inside the venue triangle/material budgets with mesh-based colliders (`collision-proxy`).
|
||||||
|
- [ ] **Path D — Distant backdrop:** test a baked panorama, impostor or billboard generated from
|
||||||
|
the splat for skyline and opposite-street depth where the player cannot walk; measure file
|
||||||
|
size, loading and visible pop at the venue boundary.
|
||||||
|
- [ ] **Path E — User-photo supplement:** let a small supplied photo set improve one facade or
|
||||||
|
landmark (a signage patch, colour reference or hero detail) without rebuilding the venue;
|
||||||
|
define the minimum evidence and the same licence/privacy/review gate as a full capture.
|
||||||
|
- [ ] For every path record quality gain versus effort, licence compatibility of the derived
|
||||||
|
output, and whether the result may ship or stays development-only. Prefer the cheapest
|
||||||
|
path that closes the visible gap.
|
||||||
|
|
||||||
|
### Gaussian splatting: runtime evaluation and decision
|
||||||
|
|
||||||
|
- [ ] Probe Godot 4.7.2 Compatibility and Web support for splat rendering in a throwaway scene:
|
||||||
|
addon or importer availability, shader/sort cost, memory, load time and fallback behaviour.
|
||||||
|
Do not add a splat dependency to the release project during evaluation.
|
||||||
|
- [ ] Set hard limits before any runtime experiment: maximum per-splat file size and total
|
||||||
|
memory, allowed frame-time delta on the 24-NPC budget scene, acceptable Web load time, and
|
||||||
|
no compute-shader requirement for the Compatibility/Web path.
|
||||||
|
- [ ] Keep collision and navigation mesh-based even if a splat is displayed; a splat must never
|
||||||
|
become the playable floor or the access/occlusion authority.
|
||||||
|
- [ ] Write the ADR with a per-path go/no-go, the accepted licence/provenance rules, whether each
|
||||||
|
result ships or stays development-only, rejected alternatives and the evidence bundle.
|
||||||
|
- [ ] Exit: one licensed photo capture measurably improves a venue through a documented path and
|
||||||
|
passes visual, performance and provenance review; or the approach is rejected with
|
||||||
|
reproducible findings and removed from the release path.
|
||||||
|
|
||||||
|
### QA and exit
|
||||||
|
|
||||||
|
- [ ] Compare the data-backed block against the existing screenshots and map documentation; keep
|
||||||
|
the stylized fallback available.
|
||||||
|
- [ ] Run the venue triangle/mesh budget, navigation and collider checks, plus day/night capture
|
||||||
|
comparisons.
|
||||||
|
- [ ] Update `docs/architecture.md`, the venue map documents, `docs/asset-tooling.md`,
|
||||||
|
`docs/development-status.md` and `CHANGELOG.md` when the pipeline ships or a source is
|
||||||
|
rejected.
|
||||||
|
- [ ] Exit: one local block is reconstructed from licensed data with reference-derived photoreal
|
||||||
|
facades, passes performance/navigation/visual QA and has complete provenance; or the
|
||||||
|
pipeline is rejected with the findings recorded.
|
||||||
|
|
||||||
|
## Maintenance and documentation hygiene
|
||||||
|
|
||||||
|
Unversioned housekeeping that keeps the planning sources trustworthy. Do these when they block a
|
||||||
|
task, not on a schedule.
|
||||||
|
|
||||||
|
### Memory and status accuracy
|
||||||
|
|
||||||
|
- [ ] Trim `MEMORY.md` from ~970 lines to the durable set: drop the pre-0.8 host/disk/NSIS/release
|
||||||
|
anecdotes, superseded v0.2.x handoff status and duplicated release notes, keep the gotchas
|
||||||
|
that still bite (async/event wiring, import order, colliders, RNG, contracts), and keep the
|
||||||
|
easter-egg note.
|
||||||
|
- [ ] Re-check the remaining memory claims against code before deleting them; `MAX_LIVE_NPCS` is
|
||||||
|
still 16 for gameplay while the separate budget smoke spawns 24, so do not "fix" that.
|
||||||
|
- [ ] Fix stale status claims as they are found (the step-collider and flashlight-beam claims were
|
||||||
|
corrected on 2026-11-03); add a documentation check or grep note only if the same class of
|
||||||
|
drift returns.
|
||||||
|
|
||||||
|
### Blender skills upkeep
|
||||||
|
|
||||||
|
- [ ] Re-check the installed Blender pack (`version.json`) when updating it and record the version
|
||||||
|
in `AGENTS.md` if it changes; keep `.agents/` gitignored and `.agents/.gdignore` in place.
|
||||||
|
- [ ] Prune the skill routes in `AGENTS.md` if a skill stops matching the project (for example if
|
||||||
|
a future second-rig path makes `rigging`/`animation` guidance authoritative).
|
||||||
|
|
||||||
|
## Gap Analysis and 0.x Roadmap
|
||||||
|
|
||||||
|
Baseline: **v0.9.6** (2026-11-03). Three playable venues, tutorial/campaign/checkpoint resume,
|
||||||
|
difficulty/help, audio/accessibility settings, a 24-NPC budget baseline, a complete
|
||||||
|
escalation/access/radio loop, shift review, the Blender asset library, 272 headless + 108 Python
|
||||||
|
tests, a 21/21 clean screen-design judge and an optional Forward+ Desktop HQ build. This is the
|
||||||
|
baseline; 0.10.0-rc.1 is the current candidate source, with release approvals still open.
|
||||||
|
|
||||||
|
### Documentation audit (completed 2026-11-01)
|
||||||
|
|
||||||
|
- [x] Make `docs/architecture.md` the single architecture source: asset/content pipeline
|
||||||
|
contracts, runtime venue-switch/fallback flows, deployment/version constraints, quality
|
||||||
|
requirements, risk register and documentation ownership map.
|
||||||
|
- [x] Keep command-heavy operational pages separate but subordinate: `docs/asset-tooling.md`,
|
||||||
|
`docs/blender-assets.md`, `docs/getting-started.md`, `docs/RELEASING.md` and
|
||||||
|
`docs/smoke-checklist.md` link to architecture rather than acting as parallel sources.
|
||||||
|
- [x] Remove the obsolete `docs/plan-graphics-v0.6.md` and its links; completed graphics work is
|
||||||
|
represented by ADR-0013, the asset pipeline section and the changelog.
|
||||||
|
- [x] Resolve stale claims: README release version/download row, development status
|
||||||
|
"Forward+ not implemented", and Blender props "not AO-baked".
|
||||||
|
- [x] Record the documentation ownership rule in `docs/architecture.md` §12: design intent
|
||||||
|
(`PLAN.md`), mechanics (`gameplay.md`), system architecture/contracts (`architecture.md`),
|
||||||
|
decisions (ADRs), status (`development-status.md`), roadmap (`TODO.md`), operations
|
||||||
|
(tooling/release docs).
|
||||||
|
|
||||||
|
### Gap register (remaining roadmap)
|
||||||
|
|
||||||
|
| Area | Current baseline | Remaining gap | Risk | Milestone |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 3D model re-evaluation | 8 characters, 18 gear, 20+6 props audited against budgets/proportions; no skill-guided scorecard yet | Scorecard, ranked P0/P1 fixes, texel/weight/collider measurements and accept decisions | Unmeasured drift stays in the shipped library | Unversioned |
|
||||||
|
| Character development | 8-variant library; source/licence review and Quaternius/MakeHuman evaluation complete | Four crowd variants, role pools, gear fit and runtime integration | Repetition remains or external assets break the contract | Unversioned |
|
||||||
|
| Local map development | Stylized venue geometry and reference maps | OSM/3D data, photoreal references, AI-assisted composition and splat evaluation | Licensing/provenance or performance failure | Unversioned |
|
||||||
|
| Content and balance | 3 venues with validated 12+ event matrices, recovery events and contextual dialogue | External playtest evidence, balance/fairness pass, event-weight tuning and final content lock | Repetition or unfair outcomes survive into the release candidate | 0.10.0 |
|
||||||
|
| Dialog/UI | Audited terminology, copy, button states, focus/navigation, modal close, long strings and all 21 screen captures | Keep the capture/compare/judge gate current as content changes | Low; the remaining risk is drift after new UI is added | Complete (0.9.3) |
|
||||||
|
| Animation/presentation | Procedural pose layers, portrait captures, reduced hem clipping, 8,000/8,000 NPC cap | Thorough NPC silhouette/face/gear/pose/LOD optimization before visual QA | Close-up jank reduces perceived quality | Complete (0.9.4) |
|
||||||
|
| Audio/accessibility | Procedural ambience/SFX, volume controls, captions/reduce-motion settings, searchable Help | Full accessibility/localization acceptance review, audio caption coverage and P0/P1 fixes | Public release cannot claim accessible play | 0.10.0 |
|
||||||
|
| Localization | English JSON table, validation and input-generated Help | Pseudo-locale/layout audit and translator workflow sign-off | Text expansion/clipping at release | 0.10.0 |
|
||||||
|
| Performance/platform | 24-NPC headless budget (6.94 ms), Compatibility/Web/HQ paths | Target hardware matrix, Web/HQ manual profiling, frame-time/memory evidence | Platform-specific drops or startup failures | 0.9.6 / 0.10.0 |
|
||||||
|
| Save lifecycle | Settings/campaign/history/checkpoint files, atomic writes | Migration fixtures from 0.8/0.9, fresh-install/upgrade verification, rollback instructions | Player progress loss on upgrade | 0.10.0 |
|
||||||
|
| QA/CI | Headless suite, smokes, screenshot compare, 21/21 judge | Release-candidate smoke matrix, final gallery and release dry run | Regressions escape the release gate | 0.9.6 / 0.10.0 |
|
||||||
|
| Release/legal | Unsigned desktop/macOS builds, Web/HQ packages | Signing/notarization decision, checksums, AI-asset provenance, credits, privacy/body-camera and rating review | Public release blocked by trust/policy | 0.10.0 |
|
||||||
|
| Documentation | Architecture/ADR/status/roadmap ownership established | Final README/changelog/status/TODO/smoke-checklist sign-off and known-limitations page | Docs drift at launch | 0.9.6 / 0.10.0 |
|
||||||
|
| Future 0.x | Stable 0.x feature set | Live replay, more venues, VO/music, image-to-mesh props and seasonal content | Scope creep before release | Unscheduled |
|
||||||
|
|
||||||
|
**Current priority:** the unversioned **3D model re-evaluation** runs first because its findings
|
||||||
|
gate the crowd-variant roster; **Character development** and **Local map development** follow.
|
||||||
|
The **0.10.0 release candidate** is the next versioned release; content lock, external playtest,
|
||||||
|
balance, save migration and release/legal sign-off remain tracked there. The source version is
|
||||||
|
0.10.0-rc.1; the v0.9.6 release is published. Earlier map/NPC/UI/texture/integration passes are
|
||||||
|
complete and in `DONE.md`.
|
||||||
|
|
||||||
|
## Milestone 0.10.0 — Release candidate
|
||||||
|
|
||||||
|
Goal: freeze the feature set, prove it with external players, clear release/legal blockers and
|
||||||
|
verify every distribution path. This remains the next versioned release after the 0.9.x patch
|
||||||
|
line, but follows the unversioned 3D re-evaluation, character and local-map tracks in planning
|
||||||
|
order. Only a release-blocking defect from those tracks may enter this milestone before the tag.
|
||||||
|
|
||||||
|
### Content lock and external playtest
|
||||||
|
|
||||||
|
- [x] Declare a content/feature freeze for 0.10.0; only release-blocking fixes and balance changes
|
||||||
|
may land after the freeze. (Frozen 2026-11-03 after the 0.9.6 integration
|
||||||
|
checkpoint; this pass is limited to release reliability, accessibility,
|
||||||
|
balance evidence and packaging.)
|
||||||
|
- [x] Produce a release-candidate build with the tutorial, campaign, all three venues, Help,
|
||||||
|
accessibility settings and checkpoint/resume enabled. (Clean worktree
|
||||||
|
`a9658ec` built `0.10.0-rc.1`: Linux 161.5 MB, Windows 197.2 MB, macOS
|
||||||
|
148.0 MB, Web 97.8 MB and HQ 241.6 MB. The Web/HQ archives include the
|
||||||
|
credits, privacy, accessibility and known-limitations pages; SHA-256
|
||||||
|
manifests were written; the Linux and HQ binaries launched headless and the
|
||||||
|
Web index/wasm/pck/docs returned HTTP 200. Not published because external
|
||||||
|
playtest and legal approval remain open.)
|
||||||
|
- [ ] Run at least five first-time-player sessions covering the tutorial and one full venue shift;
|
||||||
|
record where players get stuck, misunderstand evidence or choose disproportionate escalation.
|
||||||
|
- [ ] Triage findings by severity (P0/P1/P2) and fix every P0/P1 onboarding/accessibility issue;
|
||||||
|
keep the rest as explicit known limitations or post-0.10 items.
|
||||||
|
- [ ] Re-run the affected tutorial/campaign smokes after each fix and capture a fresh first-hour
|
||||||
|
walkthrough for the release notes.
|
||||||
|
|
||||||
|
### Balance and fairness pass
|
||||||
|
|
||||||
|
- [x] Define measurable targets for routine compliance, access-decision accuracy, peaceful
|
||||||
|
resolution, justified emergency calls and false-positive/false-negative rates.
|
||||||
|
(`docs/balance-acceptance.md` defines cohorts, denominators, 36 matrix cells,
|
||||||
|
seeds, thresholds and matched emergency-first comparisons. Documentation verified;
|
||||||
|
targets are not measured results.)
|
||||||
|
- [ ] Run scripted venue sweeps across easy/normal/hard and all four phases; compare outcomes and
|
||||||
|
tune event weights, reaction tables, compliance and report strictness where targets fail.
|
||||||
|
- [ ] Review emergency calls against actual danger: no routine behavior should make emergency the
|
||||||
|
dominant winning move, and genuine threats must remain answerable. Include the
|
||||||
|
type-derived `_read_npc_truth()` mismatch with observed threats/forced entry;
|
||||||
|
use independently labelled behavior evidence, not the helper as its own oracle.
|
||||||
|
- [ ] Verify the reputation grades, debrief explanations and campaign progression still communicate
|
||||||
|
the de-escalation-first design after tuning.
|
||||||
|
- [ ] Record the balance evidence and any deliberate exceptions in `docs/development-status.md`
|
||||||
|
or `MEMORY.md`.
|
||||||
|
|
||||||
|
### Save migration and session reliability
|
||||||
|
|
||||||
|
- [x] Add fixtures for a 0.8-era settings/campaign/history save and a 0.9.0 checkpoint; verify
|
||||||
|
migration/default-filling and a clean first launch with no files. (Committed
|
||||||
|
fixtures under `src/test/fixtures/saves/`; 9 migration/failure tests cover
|
||||||
|
old settings/campaign/history, the 0.9.0 checkpoint, all three venue
|
||||||
|
checkpoint round-trips, clean install, corrupt JSON and a blocked atomic
|
||||||
|
replace. Suite is now 272 tests.)
|
||||||
|
- [x] Test fresh install → tutorial → campaign → pause → quit → resume → review → next venue on
|
||||||
|
an isolated user-data directory. (Tutorial and campaign smokes shared one
|
||||||
|
isolated `XDG_DATA_HOME`; tutorial completion/replay/skip then campaign
|
||||||
|
resume/review-continue/help both passed. Pause/menu behavior is covered by
|
||||||
|
`smoke_session_shell.gd`.)
|
||||||
|
- [x] Test corrupt JSON, partial temp files, missing optional fields and a failed atomic rename;
|
||||||
|
confirm fallback behavior never destroys a valid save. (Corrupt/partial files
|
||||||
|
fall back to defaults; a blocked temp path reports an error and leaves the
|
||||||
|
prior valid settings intact. `_load_json` now uses the non-logging JSON
|
||||||
|
instance parser.)
|
||||||
|
- [x] Verify checkpoint resume after all three venues, after an incident, with NPCs at the door and
|
||||||
|
with flashlight/body-camera/door state set. (The migration suite round-trips a
|
||||||
|
checkpoint for Hollywood, the Joke Shop and the TV Studio with an open
|
||||||
|
incident, one NPC row, flashlight/body-camera/door flags; the campaign smoke
|
||||||
|
resumes a live checkpoint through the real `GameManager`.)
|
||||||
|
- [x] Document save locations, schema versions, reset behavior and rollback/export instructions in
|
||||||
|
`docs/getting-started.md` and the release notes. (`getting-started.md` now
|
||||||
|
documents the four files, version-1 checkpoint, atomic write/fallback,
|
||||||
|
clean install, reset and backup/rollback.)
|
||||||
|
|
||||||
|
### Release engineering and distribution
|
||||||
|
|
||||||
|
- [x] Run `tools/build_releases.sh` from a clean checkout and verify Linux/Windows/macOS/Web/HQ
|
||||||
|
artifact names, sizes and checksums. (Clean worktree `a9658ec` built the
|
||||||
|
five artifacts listed above; `PUBLISH_DRY_RUN=1` produced and printed
|
||||||
|
`build/SHA256SUMS-0.10.0-rc.1.txt` without touching the API.)
|
||||||
|
- [ ] Smoke-test fresh Linux portable/AppImage, Windows portable/installer, macOS unzip/launch,
|
||||||
|
Web static hosting and Desktop HQ Vulkan fallback on the target matrix.
|
||||||
|
(Linux portable and Desktop HQ launched headless; Web files served over a
|
||||||
|
local static server with HTTP 200. AppImage/installer were not built locally
|
||||||
|
and Windows/macOS launch testing still needs the target matrix.)
|
||||||
|
- [x] Add a disk-space preflight and retention policy to the release/publish scripts so a full
|
||||||
|
Gitea host cannot leave a partially uploaded release. (`publish_release.sh`
|
||||||
|
checks artifact size/free space, writes SHA-256 checksums, supports
|
||||||
|
`PUBLISH_DRY_RUN`, rolls back uploaded assets/new releases on failure, and
|
||||||
|
offers opt-in `RETAIN_RELEASES` pruning; dry-run verified.)
|
||||||
|
- [x] Verify release metadata, tag/version consistency, asset counts and download links; retain
|
||||||
|
at least the latest 0.8/0.9 releases and document older-asset pruning.
|
||||||
|
(Source/version and tag are both 0.9.6; all seven published releases carry
|
||||||
|
5–7 assets, including the retained v0.8.0/v0.8.1/v0.9.0/v0.9.1 line.
|
||||||
|
`publish_release.sh` documents opt-in `RETAIN_RELEASES` pruning and
|
||||||
|
`RELEASING.md` describes the seven-release policy.)
|
||||||
|
- [x] Decide and document whether macOS signing/notarization is required for 0.10.0 or explicitly
|
||||||
|
remains a future release blocker; record the quarantine instructions either way.
|
||||||
|
(0.10.0 ships unsigned with quarantine instructions; signing/notarization
|
||||||
|
remains an explicit public-release blocker in `RELEASING.md` and
|
||||||
|
`known-limitations.md`.)
|
||||||
|
|
||||||
|
### Legal, credits and platform policy
|
||||||
|
|
||||||
|
- [x] Audit AI-assisted asset provenance and licenses; record source/model usage and any
|
||||||
|
redistribution constraints for textures, characters, props and UI art.
|
||||||
|
(`CREDITS.md` records the Blender/OpenRouter toolchain, default model slugs,
|
||||||
|
missing per-file generation metadata and the commercial-rights review as an
|
||||||
|
explicit release blocker.)
|
||||||
|
- [x] Audit audio/SFX/music licensing (or confirm everything is procedural) and add a credits file.
|
||||||
|
(All runtime audio is procedural; no external music/SFX files are bundled.
|
||||||
|
`CREDITS.md` records this.)
|
||||||
|
- [x] Review body-camera/privacy language, mental-health-crisis representation, age/content rating
|
||||||
|
and storefront requirements; fix any release-blocking copy or content.
|
||||||
|
(`PRIVACY.md` clarifies the simulated body camera/local-only data; the
|
||||||
|
accessibility and known-limitations pages record the remaining
|
||||||
|
mental-health/rating/storefront review as a release blocker rather than
|
||||||
|
claiming approval.)
|
||||||
|
- [x] Add the player-facing credits, known limitations, accessibility statement and privacy note to
|
||||||
|
the release package/README. (`README.md` links `CREDITS.md`, `PRIVACY.md`,
|
||||||
|
`docs/accessibility.md` and `docs/known-limitations.md`; the Web and Desktop
|
||||||
|
HQ package builders copy those documents into the shipped archives.)
|
||||||
|
- [ ] Mark external legal/storefront approval as complete or explicitly block the 0.10.0 release on it.
|
||||||
|
|
||||||
|
### Documentation and visual sign-off
|
||||||
|
|
||||||
|
- [ ] Update `README.md`, `PLAN.md`, `docs/architecture.md`, ADRs, `docs/development-status.md`,
|
||||||
|
`docs/getting-started.md`, `docs/smoke-checklist.md`, `CHANGELOG.md` and this roadmap.
|
||||||
|
- [ ] Regenerate the full screenshot gallery and realism baselines after content lock; run
|
||||||
|
`check_screenshots.sh` and the pinned vision judge, recording model/spend/verdicts.
|
||||||
|
- [ ] Run the manual smoke checklist on every target and attach/link the release-candidate evidence.
|
||||||
|
- [ ] Verify the tutorial/campaign/help text at maximum font scale and with the longest strings.
|
||||||
|
- [x] Remove or archive stale plans/notes and confirm no contradictory version/status claims remain.
|
||||||
|
- [x] Document the character development toolchain in `docs/architecture.md`: base vs library
|
||||||
|
sources, tool/artifact/gate table, runtime attach/animation contracts and rebuild discipline.
|
||||||
|
(Added §5 "Character development tooling" plus risk and ownership-map updates;
|
||||||
|
documentation check passed.)
|
||||||
|
|
||||||
|
### Release-candidate QA and exit
|
||||||
|
|
||||||
|
- [ ] Run the complete headless suite, Python tooling, tutorial/campaign/session/scenario smokes,
|
||||||
|
24-NPC budget smoke and release build from a clean checkout.
|
||||||
|
- [ ] Run the manual QA matrix: fresh install, upgrade, offline launch, gamepad, accessibility
|
||||||
|
settings, pause/resume, all venues and Desktop HQ fallback.
|
||||||
|
- [ ] Confirm no P0/P1 defects remain; P2 items must be documented as known limitations or
|
||||||
|
post-0.10 work.
|
||||||
|
- [ ] Create the `v0.10.0` version bump, changelog entry and tag; build/publish the release and
|
||||||
|
verify all assets and download links.
|
||||||
|
- [ ] Exit: the release candidate is content-locked, externally played, legally reviewed,
|
||||||
|
documented, reproducibly built, and approved as the 0.10.0 release candidate.
|
||||||
|
|
||||||
|
## Roadmap dependencies and risks
|
||||||
|
|
||||||
|
- Content volume is the largest schedule risk; tutorial and campaign work must not outrun the
|
||||||
|
event/dialogue matrix.
|
||||||
|
- The vision judge is a dev-time aid with an external key/budget; CI must never make it a hard gate.
|
||||||
|
- Godot version changes can alter lighting/imports; do not combine an engine baseline move with a
|
||||||
|
content/feature milestone.
|
||||||
|
- Signed macOS builds and legal/AI provenance are release blockers, not polish.
|
||||||
|
- Photo-derived map assets add a second provenance risk: downloaded or user-supplied photos can
|
||||||
|
carry unclear licences, identifiable people, plates and property restrictions. No photo or
|
||||||
|
splat-derived asset ships without a completed sidecar, consent/licence review and redaction.
|
||||||
|
- Gaussian-splat training is GPU/disk-heavy and the toolchain churns quickly; keep it offline,
|
||||||
|
version-pinned and outside CI, and never make a runtime splat renderer a release dependency.
|
||||||
|
- Keep the de-escalation-first rules and rare-emergency design unchanged while adding progression,
|
||||||
|
difficulty and feedback.
|
||||||
|
|
||||||
|
- The Blender skill pack is local, unversioned guidance; it can drift from the project contract or
|
||||||
|
assume Blender MCP. Re-verify every adopted technique with `inspect`/`audit`/`fit` and never
|
||||||
|
make a skill, asset pack or tool version a build or CI dependency.
|
||||||
|
- The 3D re-evaluation can expand without bound (retopo every head, rig every finger). Cap it with
|
||||||
|
the scorecard ranking: P0/P1 only, one asset family per commit, and an explicit accept for
|
||||||
|
everything else.
|
||||||
|
|
||||||
|
## Future / deferred (not scheduled)
|
||||||
|
|
||||||
|
- [ ] Live 3D body-camera replay; the timeline is text-only today.
|
||||||
|
- [ ] Additional venues or seasonal events authored from the `content/venues/` resources.
|
||||||
|
- [ ] Real voice/dialogue audio; ambience, cues and radio crackle are procedural.
|
||||||
- [ ] Swap the primitive prop builder for an image-to-mesh model API behind the same
|
- [ ] Swap the primitive prop builder for an image-to-mesh model API behind the same
|
||||||
`assets/models/generated/<prop>.glb` contract.
|
`assets/models/generated/<prop>.glb` contract.
|
||||||
|
- [ ] Rare easter eggs/cameos (e.g. Bob Dobbs) only after core content and QA are stable.
|
||||||
@@ -28,12 +28,12 @@
|
|||||||
"attach": "back",
|
"attach": "back",
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
0.15,
|
0.161,
|
||||||
0.3,
|
0.296,
|
||||||
0.141
|
0.212
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.15,
|
-0.161,
|
||||||
-0.12,
|
-0.12,
|
||||||
-0.319
|
-0.319
|
||||||
]
|
]
|
||||||
@@ -42,11 +42,11 @@
|
|||||||
"materials": [
|
"materials": [
|
||||||
"pack_nylon",
|
"pack_nylon",
|
||||||
"steel",
|
"steel",
|
||||||
"plastic_black"
|
"denim_bag"
|
||||||
],
|
],
|
||||||
"path": "assets/models/blender/npc_gear/backpack.glb",
|
"path": "assets/models/blender/npc_gear/backpack.glb",
|
||||||
"triangles": 316,
|
"triangles": 380,
|
||||||
"vertices": 360
|
"vertices": 408
|
||||||
},
|
},
|
||||||
"beanie": {
|
"beanie": {
|
||||||
"attach": "head",
|
"attach": "head",
|
||||||
@@ -453,7 +453,7 @@
|
|||||||
"max": [
|
"max": [
|
||||||
0.321,
|
0.321,
|
||||||
1.824,
|
1.824,
|
||||||
0.185
|
0.189
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.321,
|
-0.321,
|
||||||
@@ -467,8 +467,8 @@
|
|||||||
"skin",
|
"skin",
|
||||||
"cloth",
|
"cloth",
|
||||||
"pants",
|
"pants",
|
||||||
"eye_white",
|
|
||||||
"accessory",
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
"hair",
|
"hair",
|
||||||
"rubber"
|
"rubber"
|
||||||
],
|
],
|
||||||
@@ -487,15 +487,15 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 3974,
|
"triangles": 4482,
|
||||||
"vertices": 3744
|
"vertices": 4260
|
||||||
},
|
},
|
||||||
"npc_coat": {
|
"npc_coat": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
0.332,
|
0.332,
|
||||||
1.824,
|
1.824,
|
||||||
0.185
|
0.189
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.332,
|
-0.332,
|
||||||
@@ -509,8 +509,8 @@
|
|||||||
"skin",
|
"skin",
|
||||||
"cloth",
|
"cloth",
|
||||||
"pants",
|
"pants",
|
||||||
"eye_white",
|
|
||||||
"accessory",
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
"hair",
|
"hair",
|
||||||
"rubber"
|
"rubber"
|
||||||
],
|
],
|
||||||
@@ -528,8 +528,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 5026,
|
"triangles": 5474,
|
||||||
"vertices": 4807
|
"vertices": 5264
|
||||||
},
|
},
|
||||||
"npc_dress": {
|
"npc_dress": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -550,8 +550,8 @@
|
|||||||
"skin",
|
"skin",
|
||||||
"cloth",
|
"cloth",
|
||||||
"pants",
|
"pants",
|
||||||
"eye_white",
|
|
||||||
"accessory",
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
"hair",
|
"hair",
|
||||||
"rubber"
|
"rubber"
|
||||||
],
|
],
|
||||||
@@ -567,8 +567,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4428,
|
"triangles": 4900,
|
||||||
"vertices": 4026
|
"vertices": 4719
|
||||||
},
|
},
|
||||||
"npc_firefighter": {
|
"npc_firefighter": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -589,8 +589,8 @@
|
|||||||
"skin",
|
"skin",
|
||||||
"cloth",
|
"cloth",
|
||||||
"pants",
|
"pants",
|
||||||
"eye_white",
|
|
||||||
"accessory",
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
"hair",
|
"hair",
|
||||||
"hivis",
|
"hivis",
|
||||||
"reflective",
|
"reflective",
|
||||||
@@ -608,15 +608,15 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4848,
|
"triangles": 5344,
|
||||||
"vertices": 4735
|
"vertices": 5494
|
||||||
},
|
},
|
||||||
"npc_guard": {
|
"npc_guard": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
0.361,
|
0.361,
|
||||||
1.819,
|
1.819,
|
||||||
0.185
|
0.189
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.361,
|
-0.361,
|
||||||
@@ -630,8 +630,8 @@
|
|||||||
"skin",
|
"skin",
|
||||||
"cloth",
|
"cloth",
|
||||||
"pants",
|
"pants",
|
||||||
"eye_white",
|
|
||||||
"accessory",
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
"hair",
|
"hair",
|
||||||
"rubber",
|
"rubber",
|
||||||
"leather",
|
"leather",
|
||||||
@@ -652,15 +652,15 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4560,
|
"triangles": 5046,
|
||||||
"vertices": 4529
|
"vertices": 5096
|
||||||
},
|
},
|
||||||
"npc_hoodie": {
|
"npc_hoodie": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
0.326,
|
0.326,
|
||||||
1.856,
|
1.856,
|
||||||
0.185
|
0.189
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.326,
|
-0.326,
|
||||||
@@ -674,8 +674,8 @@
|
|||||||
"skin",
|
"skin",
|
||||||
"cloth",
|
"cloth",
|
||||||
"pants",
|
"pants",
|
||||||
"eye_white",
|
|
||||||
"accessory",
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
"hair",
|
"hair",
|
||||||
"rubber"
|
"rubber"
|
||||||
],
|
],
|
||||||
@@ -693,15 +693,15 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4826,
|
"triangles": 5304,
|
||||||
"vertices": 4504
|
"vertices": 4950
|
||||||
},
|
},
|
||||||
"npc_jacket": {
|
"npc_jacket": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
0.332,
|
0.332,
|
||||||
1.824,
|
1.824,
|
||||||
0.185
|
0.189
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.332,
|
-0.332,
|
||||||
@@ -715,8 +715,8 @@
|
|||||||
"skin",
|
"skin",
|
||||||
"cloth",
|
"cloth",
|
||||||
"pants",
|
"pants",
|
||||||
"eye_white",
|
|
||||||
"accessory",
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
"hair",
|
"hair",
|
||||||
"rubber"
|
"rubber"
|
||||||
],
|
],
|
||||||
@@ -734,15 +734,15 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4394,
|
"triangles": 4872,
|
||||||
"vertices": 4175
|
"vertices": 4661
|
||||||
},
|
},
|
||||||
"npc_vest": {
|
"npc_vest": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
0.321,
|
0.321,
|
||||||
1.819,
|
1.819,
|
||||||
0.185
|
0.189
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.321,
|
-0.321,
|
||||||
@@ -756,8 +756,8 @@
|
|||||||
"skin",
|
"skin",
|
||||||
"cloth",
|
"cloth",
|
||||||
"pants",
|
"pants",
|
||||||
"eye_white",
|
|
||||||
"accessory",
|
"accessory",
|
||||||
|
"eye_white",
|
||||||
"hair",
|
"hair",
|
||||||
"rubber",
|
"rubber",
|
||||||
"hivis",
|
"hivis",
|
||||||
@@ -775,8 +775,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4474,
|
"triangles": 4980,
|
||||||
"vertices": 4340
|
"vertices": 4879
|
||||||
},
|
},
|
||||||
"palm_tree": {
|
"palm_tree": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 1003 B After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 897 B After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"hud.controls": "LMB/Space wheel · A/D step · Space confirm · Esc cancel · R emergency · E inspect · C wand · Q polite · B camera · Tab/wheel cycle · Shift slow · G light · L lock · F debug",
|
"hud.controls": "LMB/Space wheel · A/D step · Space confirm · Esc cancel · R emergency · E inspect · C wand · Q ask · B camera · Tab/wheel cycle · Shift slow · G light · L lock · F debug",
|
||||||
"radial.raise_hand": "Stop",
|
"radial.raise_hand": "Stop",
|
||||||
"radial.eye_contact": "Look",
|
"radial.eye_contact": "Look",
|
||||||
"radial.point": "Point",
|
"radial.point": "Point",
|
||||||
@@ -88,5 +88,122 @@
|
|||||||
"review.timeline_button": "Body camera timeline",
|
"review.timeline_button": "Body camera timeline",
|
||||||
"hud.nearby": "%d nearby · Tab or mouse wheel cycles targets",
|
"hud.nearby": "%d nearby · Tab or mouse wheel cycles targets",
|
||||||
"hud.precision": "Precision walk (Shift)",
|
"hud.precision": "Precision walk (Shift)",
|
||||||
"hud.controls_gamepad": "Left stick move · Right stick look · A wheel/confirm · B cancel · X inspect · Y radio · LB slow · RB wand"
|
"hud.controls_gamepad": "Left stick move · Right stick look · A wheel/confirm · B cancel · X inspect · Y radio · LB slow · RB wand",
|
||||||
|
"tutorial.header": "FIRST SHIFT",
|
||||||
|
"tutorial.skip_hint": "F1 skip tutorial",
|
||||||
|
"tutorial.arrive": "Walk forward and look around. The entrance is behind you.",
|
||||||
|
"tutorial.focus": "Look at the visitor. Press Tab or the mouse wheel to cycle targets.",
|
||||||
|
"tutorial.wheel": "Open the action wheel with Space or left click.",
|
||||||
|
"tutorial.inspect": "Choose Inspect and read the access evidence.",
|
||||||
|
"tutorial.direction": "Close the panel and give a polite direction.",
|
||||||
|
"tutorial.warning": "Give a formal warning if the visitor persists. It arms the body camera.",
|
||||||
|
"tutorial.radio": "Open the assistance radio and submit a short report.",
|
||||||
|
"tutorial.review": "Finish the shift and review what happened.",
|
||||||
|
"tutorial.completed": "Tutorial complete.",
|
||||||
|
"ui.inspect_actions": "A = admit · D = refuse entry",
|
||||||
|
"ui.wand_actions": "A = admit · D = refuse entry",
|
||||||
|
"ui.radio_send_assist": "Send request",
|
||||||
|
"ui.radio_send_emergency": "Send emergency",
|
||||||
|
"ui.radio_hold_hint": "Hold the button for %.1fs",
|
||||||
|
"ui.radio_cancelled": "Call not sent — hold the button to confirm.",
|
||||||
|
"ui.radio_holding": "Holding… %.0f%%",
|
||||||
|
"ui.review_return_menu": "Return to menu",
|
||||||
|
"ui.quit_game": "Quit game",
|
||||||
|
"ui.apply_settings": "Apply settings",
|
||||||
|
"tutorial.skipped": "Tutorial skipped.",
|
||||||
|
"ui.settings_title": "Settings",
|
||||||
|
"ui.settings_invert": "Invert vertical look",
|
||||||
|
"ui.settings_head_bob": "Head bob and camera motion",
|
||||||
|
"ui.settings_difficulty": "Difficulty / pacing",
|
||||||
|
"ui.settings_reduce_motion": "Reduce motion",
|
||||||
|
"ui.settings_captions": "Captions / subtitles",
|
||||||
|
"ui.settings_replay": "Replay tutorial",
|
||||||
|
"ui.settings_reset_campaign": "Reset campaign",
|
||||||
|
"ui.settings_controls_help": "Controls (click a binding, then press a key or pad button)",
|
||||||
|
"ui.settings_cancel": "Cancel",
|
||||||
|
"ui.settings_reset_title": "Reset campaign",
|
||||||
|
"ui.settings_reset_confirm": "Clear campaign progress and restart from the Hollywood Boulevard shift?",
|
||||||
|
"ui.settings_replay_title": "Replay tutorial",
|
||||||
|
"ui.settings_replay_confirm": "Replay the tutorial from the Hollywood Boulevard shift? The current shift will end.",
|
||||||
|
"ui.settings_press": "Press…",
|
||||||
|
"ui.pause_title": "Paused",
|
||||||
|
"ui.pause_resume": "Resume",
|
||||||
|
"ui.pause_restart": "Restart shift",
|
||||||
|
"ui.pause_settings": "Settings",
|
||||||
|
"ui.pause_menu": "Quit to menu",
|
||||||
|
"ui.pause_hint": "Esc resumes",
|
||||||
|
"ui.help_title": "Help",
|
||||||
|
"ui.help_filter": "Filter controls…",
|
||||||
|
"ui.help_close": "Close",
|
||||||
|
"help.control_move": "Movement: W/A/S/D · mouse look · Shift precision walk",
|
||||||
|
"help.control_focus": "Focus: crosshair cone · Tab / mouse wheel cycles nearby people",
|
||||||
|
"help.control_wheel": "Action wheel: Space or left click · A/D or arrows step · Space confirms",
|
||||||
|
"help.control_access": "Inspect access: E · Wand: C · Ask: Q",
|
||||||
|
"help.control_escalate": "Warning: 4 · Blocking: 5 · Security radio: 6",
|
||||||
|
"help.control_emergency": "Emergency radio: R, hold the on-screen button to confirm",
|
||||||
|
"help.control_tools": "Body camera: B · Flashlight: G · Door lock: L",
|
||||||
|
"help.control_misc": "Pause: Esc · Debug overlay: F",
|
||||||
|
"ui.start_title": "GIG LINE",
|
||||||
|
"ui.start_subtitle": "Pick a venue to start your shift.",
|
||||||
|
"ui.start_help": "Help",
|
||||||
|
"ui.start_settings": "Settings",
|
||||||
|
"ui.start_hint": "Up/Down to choose · Enter to start · Esc to quit",
|
||||||
|
"ui.campaign_complete": "Campaign complete — pick any venue for a free shift.",
|
||||||
|
"ui.campaign_status": "Campaign shift %d · next: %s",
|
||||||
|
"ui.continue_shift": "Continue shift — %s",
|
||||||
|
"ui.campaign_complete_short": "Campaign complete",
|
||||||
|
"ui.radio_location_placeholder": "Location (e.g. front steps)",
|
||||||
|
"ui.radio_location_default": "front entrance",
|
||||||
|
"ui.radio_description_placeholder": "Describe the person and their behavior",
|
||||||
|
"ui.radio_weapon": "Weapon seen",
|
||||||
|
"ui.radio_danger": "Danger level: ",
|
||||||
|
"ui.radio_cancel": "Cancel",
|
||||||
|
"ui.radio_target": "Target: %s",
|
||||||
|
"ui.radio_title_emergency": "EMERGENCY RADIO",
|
||||||
|
"ui.radio_title_assist": "RADIO DISPATCH",
|
||||||
|
"hud.mode": "Door: %s · Light: %s",
|
||||||
|
"hud.door_locked": "LOCKED",
|
||||||
|
"hud.door_open": "open",
|
||||||
|
"hud.light_on": "on",
|
||||||
|
"hud.light_off": "off",
|
||||||
|
"hud.rec": "REC",
|
||||||
|
"hud.cam_clock": "SHIFT %02d:%02d:%02d",
|
||||||
|
"hud.shift_status": "shift %02d:%02d phase %s\nrep %.0f crowd %.0f%%",
|
||||||
|
"hud.inspect_text": "ACCESS INSPECTION\n\nAccess check — %s\nGranted: %s",
|
||||||
|
"hud.wand_text": "Wand check — %s\nAlert: %s",
|
||||||
|
"hud.wand_items": "Carried items: %s",
|
||||||
|
"hud.yes": "YES",
|
||||||
|
"hud.no": "NO",
|
||||||
|
"hud.wand_none": "none",
|
||||||
|
"review.title": "Shift Review",
|
||||||
|
"review.venue": "Venue: %s",
|
||||||
|
"review.policy": "Policy: %s",
|
||||||
|
"review.grade": "Grade: %s Score: %.0f/100",
|
||||||
|
"review.history": "Shifts on record: %d · best score: %.0f · grades seen: %s",
|
||||||
|
"review.incidents": "Incidents handled: %d (peaceful %d)",
|
||||||
|
"review.npcs": "NPCs encountered: %d",
|
||||||
|
"review.radio": "Radio battery: %.0f%% emergency cooldown: %.0fs",
|
||||||
|
"review.crowd": "Crowd mood: %.0f%% (panic: %s)",
|
||||||
|
"review.highlights": "Highlights:",
|
||||||
|
"review.modifier": " %s (%s) %s",
|
||||||
|
"review.continue_campaign": "Continue campaign",
|
||||||
|
"review.campaign_complete": "Campaign complete. Free-play venues remain available.",
|
||||||
|
"review.campaign_next": "Campaign shift %d complete · next: %s",
|
||||||
|
"venue.hollywood.label": "Hollywood Boulevard",
|
||||||
|
"venue.hollywood.blurb": "Building entrance, tourists and late-night trouble.",
|
||||||
|
"venue.hollywood_day.label": "Hollywood Boulevard — Day",
|
||||||
|
"venue.hollywood_day.blurb": "El Capitan entrance in daylight: tourists, performers and routine traffic.",
|
||||||
|
"venue.hollywood_night.label": "Hollywood Boulevard — Night",
|
||||||
|
"venue.hollywood_night.blurb": "The same entrance after dark: banners lit, late crowd, harder to read.",
|
||||||
|
"venue.joke.label": "The Joke Shop",
|
||||||
|
"venue.joke.blurb": "Guest list, IDs and wand screening.",
|
||||||
|
"venue.studio.label": "Late Night TV Studio",
|
||||||
|
"venue.studio.blurb": "Credentials, filming boundary, celebrity arrivals.",
|
||||||
|
"hud.reasons": "Reasons: %s",
|
||||||
|
"hud.issues": "Issues: %s",
|
||||||
|
"hud.wand_suspicious": "The wand alerts on a suspicious item (%s).",
|
||||||
|
"hud.wand_ask": "Ask the guest to identify it before deciding.",
|
||||||
|
"hud.wand_harmless": "The wand alerts on a harmless item (%s).",
|
||||||
|
"hud.wand_false": "Likely a false alarm — keys, belt, or phone.",
|
||||||
|
"hud.wand_clear": "No alert. The guest appears clear."
|
||||||
}
|
}
|
||||||
@@ -1,154 +1,61 @@
|
|||||||
[gd_resource type="Resource" script_class="VenueScenario" load_steps=17 format=3]
|
[gd_resource type="Resource" script_class="VenueScenario" format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://src/core/access_policy.gd" id="1_2u28i"]
|
[ext_resource type="Script" path="res://src/core/access_policy.gd" id="1_c5x6v"]
|
||||||
[ext_resource type="Script" path="res://src/core/npc_profile.gd" id="2_acokf"]
|
[ext_resource type="Script" path="res://src/core/npc_profile.gd" id="2_7qk8j"]
|
||||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_mqiqi"]
|
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_lvgx2"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_i8fga"]
|
[sub_resource type="Resource" id="Resource_xf4wh"]
|
||||||
script = ExtResource("1_2u28i")
|
script = ExtResource("1_c5x6v")
|
||||||
required_artifacts = {
|
required_artifacts = {
|
||||||
1: true
|
1: true
|
||||||
}
|
}
|
||||||
grant_if_any_required = false
|
|
||||||
minimum_age = 0
|
|
||||||
require_id = false
|
|
||||||
wand_screening_required = false
|
|
||||||
policy_summary = "Private property. Entry requires recognition or a valid wristband."
|
policy_summary = "Private property. Entry requires recognition or a valid wristband."
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_c7lhe"]
|
[sub_resource type="Resource" id="Resource_cgh52"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "tourist"
|
id = "tourist"
|
||||||
display_name = "Tourist"
|
display_name = "Tourist"
|
||||||
npc_type = 0
|
|
||||||
temperament = 0.1
|
temperament = 0.1
|
||||||
base_compliance = 0.9
|
base_compliance = 0.9
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
|
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"deescalation": ["Okay, I'll move to the sidewalk.", "Sorry, I'll put the camera away."],
|
||||||
|
"reason": ["Just getting a photo of the entrance.", "I'm meeting friends here."],
|
||||||
|
"recognition": ["You remember me from the tour, right?"],
|
||||||
|
"refusal": ["I just want a photo, I'm not staying.", "I didn't know this entrance was restricted."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_0t6ar"]
|
[sub_resource type="Resource" id="Resource_qe635"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "confused_visitor"
|
id = "confused_visitor"
|
||||||
display_name = "Confused Visitor"
|
display_name = "Confused Visitor"
|
||||||
npc_type = 3
|
npc_type = 3
|
||||||
temperament = 0.2
|
temperament = 0.2
|
||||||
base_compliance = 0.85
|
base_compliance = 0.85
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
|
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"deescalation": ["Oh, I understand. I'll check with the box office."],
|
||||||
|
"reason": ["I thought this was the box office.", "I'm looking for the event entrance."],
|
||||||
|
"refusal": ["I'm sure the event is here. Let me check my phone."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_wyv8p"]
|
[sub_resource type="Resource" id="Resource_u5nrp"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "delivery"
|
id = "delivery"
|
||||||
display_name = "Delivery Worker"
|
display_name = "Delivery Worker"
|
||||||
npc_type = 10
|
npc_type = 10
|
||||||
temperament = 0.25
|
|
||||||
base_compliance = 0.8
|
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["Where should I leave this?"])
|
dialogue_lines = Array[String](["Where should I leave this?"])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["Drop-off for the venue. Where should I leave it?"]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_v2hc4"]
|
[sub_resource type="Resource" id="Resource_dsavb"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "street_performer"
|
id = "street_performer"
|
||||||
display_name = "Street Performer"
|
display_name = "Street Performer"
|
||||||
npc_type = 1
|
npc_type = 1
|
||||||
temperament = 0.45
|
temperament = 0.45
|
||||||
base_compliance = 0.55
|
base_compliance = 0.55
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
0: 55,
|
0: 55,
|
||||||
@@ -156,47 +63,17 @@ reactions_by_level = {
|
|||||||
3: 30
|
3: 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
context_lines = {
|
||||||
1: {
|
"reason": ["Just doing my act; I'll keep the sidewalk clear."]
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String]([])
|
|
||||||
carried_items = Array[String]([])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_lg4td"]
|
[sub_resource type="Resource" id="Resource_sexiy"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "influencer"
|
id = "influencer"
|
||||||
display_name = "Influencer"
|
display_name = "Influencer"
|
||||||
npc_type = 2
|
npc_type = 2
|
||||||
temperament = 0.5
|
temperament = 0.5
|
||||||
base_compliance = 0.5
|
base_compliance = 0.5
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
0: 40,
|
0: 40,
|
||||||
@@ -218,47 +95,18 @@ reactions_by_level = {
|
|||||||
8: 25
|
8: 25
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
|
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["Filming a quick clip for my channel."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_ijl2s"]
|
[sub_resource type="Resource" id="Resource_ghqd8"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "intoxicated"
|
id = "intoxicated"
|
||||||
display_name = "Intoxicated Pedestrian"
|
display_name = "Intoxicated Pedestrian"
|
||||||
npc_type = 4
|
npc_type = 4
|
||||||
temperament = 0.6
|
temperament = 0.6
|
||||||
base_compliance = 0.4
|
base_compliance = 0.4
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
0: 30,
|
0: 30,
|
||||||
@@ -272,41 +120,13 @@ reactions_by_level = {
|
|||||||
4: 10
|
4: 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 1.8
|
reaction_delay = 1.8
|
||||||
dialogue_lines = Array[String]([])
|
context_lines = {
|
||||||
carried_items = Array[String]([])
|
"reason": ["Looking for my friends. I think they went inside."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_eycmk"]
|
[sub_resource type="Resource" id="Resource_g8peq"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "trespasser"
|
id = "trespasser"
|
||||||
display_name = "Trespasser"
|
display_name = "Trespasser"
|
||||||
npc_type = 5
|
npc_type = 5
|
||||||
@@ -341,41 +161,13 @@ reactions_by_level = {
|
|||||||
7: 20
|
7: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
|
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["Meeting a friend inside. I'll only be a minute.", "Just cutting through."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_ggtpw"]
|
[sub_resource type="Resource" id="Resource_4xl5p"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "mental_crisis"
|
id = "mental_crisis"
|
||||||
display_name = "Person in Crisis"
|
display_name = "Person in Crisis"
|
||||||
npc_type = 7
|
npc_type = 7
|
||||||
@@ -411,47 +203,18 @@ reactions_by_level = {
|
|||||||
7: 15
|
7: 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 2.4
|
reaction_delay = 2.4
|
||||||
dialogue_lines = Array[String]([])
|
context_lines = {
|
||||||
carried_items = Array[String]([])
|
"reason": ["I just need a quiet minute.", "I'm waiting for someone."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_oa64f"]
|
[sub_resource type="Resource" id="Resource_y6282"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "violent_threat"
|
id = "violent_threat"
|
||||||
display_name = "Violent Threat"
|
display_name = "Violent Threat"
|
||||||
npc_type = 6
|
npc_type = 6
|
||||||
temperament = 0.95
|
temperament = 0.95
|
||||||
base_compliance = 0.05
|
base_compliance = 0.05
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
2: 25,
|
2: 25,
|
||||||
@@ -479,41 +242,12 @@ reactions_by_level = {
|
|||||||
7: 30
|
7: 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
context_lines = {
|
||||||
1: {
|
"reason": ["Back off. This doesn't concern you."]
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String]([])
|
|
||||||
carried_items = Array[String]([])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_7t7dh"]
|
[sub_resource type="Resource" id="Resource_75a0r"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "protester"
|
id = "protester"
|
||||||
display_name = "Protester"
|
display_name = "Protester"
|
||||||
npc_type = 12
|
npc_type = 12
|
||||||
@@ -547,66 +281,34 @@ reactions_by_level = {
|
|||||||
7: 20
|
7: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["I have every right to be on public sidewalk.", "You can't touch me, this is a lawful protest."])
|
dialogue_lines = Array[String](["I have every right to be on public sidewalk.", "You can't touch me, this is a lawful protest."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["We're demonstrating on the public sidewalk.", "This is a peaceful protest."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_8tj1k"]
|
[sub_resource type="Resource" id="Resource_ix35t"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "authorized_staff"
|
id = "authorized_staff"
|
||||||
display_name = "Staff Member"
|
display_name = "Staff Member"
|
||||||
npc_type = 8
|
npc_type = 8
|
||||||
temperament = 0.15
|
temperament = 0.15
|
||||||
base_compliance = 1.0
|
base_compliance = 1.0
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
default_reactions = {
|
||||||
1: {
|
1: {
|
||||||
0: 100
|
0: 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reaction_delay = 0.6
|
context_lines = {
|
||||||
dialogue_lines = Array[String]([])
|
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
|
||||||
carried_items = Array[String]([])
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_tnpxm"]
|
[sub_resource type="Resource" id="Resource_0mcg3"]
|
||||||
script = ExtResource("2_acokf")
|
script = ExtResource("2_7qk8j")
|
||||||
id = "authorized_guest"
|
id = "authorized_guest"
|
||||||
display_name = "Authorized Guest"
|
display_name = "Authorized Guest"
|
||||||
npc_type = 9
|
npc_type = 9
|
||||||
temperament = 0.2
|
temperament = 0.2
|
||||||
base_compliance = 0.95
|
base_compliance = 0.95
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
default_reactions = {
|
||||||
1: {
|
1: {
|
||||||
0: 92,
|
0: 92,
|
||||||
@@ -617,16 +319,16 @@ default_reactions = {
|
|||||||
2: 12
|
2: 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reaction_delay = 0.6
|
context_lines = {
|
||||||
dialogue_lines = Array[String]([])
|
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
|
||||||
carried_items = Array[String]([])
|
}
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("3_mqiqi")
|
script = ExtResource("3_lvgx2")
|
||||||
id = "hollywood_building"
|
id = "hollywood_building"
|
||||||
display_name = "Hollywood Boulevard Building"
|
display_name = "El Capitan Entertainment Centre"
|
||||||
description = "Steps and entrance of a private building off Hollywood Boulevard."
|
description = "The historic 1921 Masonic Temple at 6840 Hollywood Boulevard, now the El Capitan Entertainment Centre."
|
||||||
access_policy = SubResource("Resource_i8fga")
|
access_policy = SubResource("Resource_xf4wh")
|
||||||
equipment = Array[String](["wristband_policy", "flashlight", "radio"])
|
equipment = Array[String](["wristband_policy", "flashlight", "radio"])
|
||||||
locations = {
|
locations = {
|
||||||
"entrance": Vector3(0, 0, 1),
|
"entrance": Vector3(0, 0, 1),
|
||||||
@@ -635,7 +337,7 @@ locations = {
|
|||||||
"sidewalk": Vector3(0, 0, 9),
|
"sidewalk": Vector3(0, 0, 9),
|
||||||
"steps_end": Vector3(0, 0, 3)
|
"steps_end": Vector3(0, 0, 3)
|
||||||
}
|
}
|
||||||
npc_profiles = Array[ExtResource("2_acokf")]([SubResource("Resource_c7lhe"), SubResource("Resource_0t6ar"), SubResource("Resource_wyv8p"), SubResource("Resource_v2hc4"), SubResource("Resource_lg4td"), SubResource("Resource_ijl2s"), SubResource("Resource_eycmk"), SubResource("Resource_ggtpw"), SubResource("Resource_oa64f"), SubResource("Resource_7t7dh"), SubResource("Resource_8tj1k"), SubResource("Resource_tnpxm")])
|
npc_profiles = Array[ExtResource("2_7qk8j")]([SubResource("Resource_cgh52"), SubResource("Resource_qe635"), SubResource("Resource_u5nrp"), SubResource("Resource_dsavb"), SubResource("Resource_sexiy"), SubResource("Resource_ghqd8"), SubResource("Resource_g8peq"), SubResource("Resource_4xl5p"), SubResource("Resource_y6282"), SubResource("Resource_75a0r"), SubResource("Resource_ix35t"), SubResource("Resource_0mcg3")])
|
||||||
event_pool = Array[Dictionary]([{
|
event_pool = Array[Dictionary]([{
|
||||||
"base_weight": 1.0,
|
"base_weight": 1.0,
|
||||||
"id": "tourist_photo",
|
"id": "tourist_photo",
|
||||||
@@ -710,6 +412,33 @@ event_pool = Array[Dictionary]([{
|
|||||||
"time_range_s": Vector2(300, 800)
|
"time_range_s": Vector2(300, 800)
|
||||||
}, {
|
}, {
|
||||||
"base_weight": 1.0,
|
"base_weight": 1.0,
|
||||||
|
"id": "late_tourist",
|
||||||
|
"intent": "photo",
|
||||||
|
"kind": "routine",
|
||||||
|
"npc_type": 0,
|
||||||
|
"phase": 3,
|
||||||
|
"probability": 0.22,
|
||||||
|
"time_range_s": Vector2(700, 900)
|
||||||
|
}, {
|
||||||
|
"base_weight": 1.0,
|
||||||
|
"id": "late_guest",
|
||||||
|
"intent": "enter",
|
||||||
|
"kind": "routine",
|
||||||
|
"npc_type": 9,
|
||||||
|
"phase": 3,
|
||||||
|
"probability": 0.2,
|
||||||
|
"time_range_s": Vector2(700, 900)
|
||||||
|
}, {
|
||||||
|
"base_weight": 1.0,
|
||||||
|
"id": "late_trespasser",
|
||||||
|
"intent": "trespass",
|
||||||
|
"kind": "routine",
|
||||||
|
"npc_type": 5,
|
||||||
|
"phase": 3,
|
||||||
|
"probability": 0.14,
|
||||||
|
"time_range_s": Vector2(700, 900)
|
||||||
|
}, {
|
||||||
|
"base_weight": 1.0,
|
||||||
"hint": "hint.body_camera_reason",
|
"hint": "hint.body_camera_reason",
|
||||||
"id": "building_filming_boundary",
|
"id": "building_filming_boundary",
|
||||||
"intent": "film",
|
"intent": "film",
|
||||||
@@ -774,6 +503,15 @@ event_pool = Array[Dictionary]([{
|
|||||||
"probability": 0.28,
|
"probability": 0.28,
|
||||||
"requires_emergency": true,
|
"requires_emergency": true,
|
||||||
"time_range_s": Vector2(760, 880)
|
"time_range_s": Vector2(760, 880)
|
||||||
|
}, {
|
||||||
|
"base_weight": 1.0,
|
||||||
|
"id": "building_apology_return",
|
||||||
|
"intent": "enter",
|
||||||
|
"kind": "recovery",
|
||||||
|
"npc_type": 9,
|
||||||
|
"phase": 2,
|
||||||
|
"probability": 0.22,
|
||||||
|
"time_range_s": Vector2(620, 800)
|
||||||
}])
|
}])
|
||||||
shift_phases = {
|
shift_phases = {
|
||||||
0: {
|
0: {
|
||||||
@@ -789,6 +527,6 @@ shift_phases = {
|
|||||||
"start_s": 700.0
|
"start_s": 700.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shift_duration_s = 900.0
|
door_staff_lines = {
|
||||||
policy_summary = ""
|
"wristband_check": ["Wristband, please.", "I need to see your event wristband."]
|
||||||
door_staff_lines = {}
|
}
|
||||||
@@ -1,113 +1,73 @@
|
|||||||
[gd_resource type="Resource" script_class="VenueScenario" load_steps=13 format=3]
|
[gd_resource type="Resource" script_class="VenueScenario" format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://src/core/access_policy.gd" id="1_lxsgk"]
|
[ext_resource type="Script" path="res://src/core/access_policy.gd" id="1_3pn5m"]
|
||||||
[ext_resource type="Script" path="res://src/core/npc_profile.gd" id="2_wfc6m"]
|
[ext_resource type="Script" path="res://src/core/npc_profile.gd" id="2_l60ad"]
|
||||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_8xm7a"]
|
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_voh88"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_xha77"]
|
[sub_resource type="Resource" id="Resource_aal3l"]
|
||||||
script = ExtResource("1_lxsgk")
|
script = ExtResource("1_3pn5m")
|
||||||
required_artifacts = {
|
required_artifacts = {
|
||||||
2: true,
|
2: true,
|
||||||
3: true
|
3: true
|
||||||
}
|
}
|
||||||
grant_if_any_required = false
|
|
||||||
minimum_age = 21
|
minimum_age = 21
|
||||||
require_id = true
|
require_id = true
|
||||||
wand_screening_required = true
|
wand_screening_required = true
|
||||||
policy_summary = "Government ID and a valid ticket or reservation required. Carry items are screened."
|
policy_summary = "Government ID and a valid ticket or reservation required. Carry items are screened."
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_8ksyk"]
|
[sub_resource type="Resource" id="Resource_57xgi"]
|
||||||
script = ExtResource("2_wfc6m")
|
script = ExtResource("2_l60ad")
|
||||||
id = "tourist"
|
id = "tourist"
|
||||||
display_name = "Tourist"
|
display_name = "Tourist"
|
||||||
npc_type = 0
|
|
||||||
temperament = 0.1
|
temperament = 0.1
|
||||||
base_compliance = 0.9
|
base_compliance = 0.9
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
|
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"deescalation": ["Okay, I'll move to the sidewalk.", "Sorry, I'll put the camera away."],
|
||||||
|
"reason": ["Just getting a photo of the entrance.", "I'm meeting friends here."],
|
||||||
|
"recognition": ["You remember me from the tour, right?"],
|
||||||
|
"refusal": ["I just want a photo, I'm not staying.", "I didn't know this entrance was restricted."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_wt1op"]
|
[sub_resource type="Resource" id="Resource_r873e"]
|
||||||
script = ExtResource("2_wfc6m")
|
script = ExtResource("2_l60ad")
|
||||||
id = "confused_visitor"
|
id = "confused_visitor"
|
||||||
display_name = "Confused Visitor"
|
display_name = "Confused Visitor"
|
||||||
npc_type = 3
|
npc_type = 3
|
||||||
temperament = 0.2
|
temperament = 0.2
|
||||||
base_compliance = 0.85
|
base_compliance = 0.85
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
|
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"deescalation": ["Oh, I understand. I'll check with the box office."],
|
||||||
|
"reason": ["I thought this was the box office.", "I'm looking for the event entrance."],
|
||||||
|
"refusal": ["I'm sure the event is here. Let me check my phone."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_g1jr2"]
|
[sub_resource type="Resource" id="Resource_3xy2u"]
|
||||||
script = ExtResource("2_wfc6m")
|
script = ExtResource("2_l60ad")
|
||||||
|
id = "street_performer"
|
||||||
|
display_name = "Street Performer"
|
||||||
|
npc_type = 1
|
||||||
|
temperament = 0.45
|
||||||
|
base_compliance = 0.55
|
||||||
|
reactions_by_level = {
|
||||||
|
1: {
|
||||||
|
0: 55,
|
||||||
|
2: 15,
|
||||||
|
3: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context_lines = {
|
||||||
|
"reason": ["Just doing my act; I'll keep the sidewalk clear."]
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Resource_50i1h"]
|
||||||
|
script = ExtResource("2_l60ad")
|
||||||
id = "influencer"
|
id = "influencer"
|
||||||
display_name = "Influencer"
|
display_name = "Influencer"
|
||||||
npc_type = 2
|
npc_type = 2
|
||||||
temperament = 0.5
|
temperament = 0.5
|
||||||
base_compliance = 0.5
|
base_compliance = 0.5
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
0: 40,
|
0: 40,
|
||||||
@@ -129,47 +89,18 @@ reactions_by_level = {
|
|||||||
8: 25
|
8: 25
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
|
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["Filming a quick clip for my channel."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_ys0b0"]
|
[sub_resource type="Resource" id="Resource_nsw77"]
|
||||||
script = ExtResource("2_wfc6m")
|
script = ExtResource("2_l60ad")
|
||||||
id = "intoxicated"
|
id = "intoxicated"
|
||||||
display_name = "Intoxicated Pedestrian"
|
display_name = "Intoxicated Pedestrian"
|
||||||
npc_type = 4
|
npc_type = 4
|
||||||
temperament = 0.6
|
temperament = 0.6
|
||||||
base_compliance = 0.4
|
base_compliance = 0.4
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
0: 30,
|
0: 30,
|
||||||
@@ -183,41 +114,13 @@ reactions_by_level = {
|
|||||||
4: 10
|
4: 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 1.8
|
reaction_delay = 1.8
|
||||||
dialogue_lines = Array[String]([])
|
context_lines = {
|
||||||
carried_items = Array[String]([])
|
"reason": ["Looking for my friends. I think they went inside."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_bv0jw"]
|
[sub_resource type="Resource" id="Resource_20knl"]
|
||||||
script = ExtResource("2_wfc6m")
|
script = ExtResource("2_l60ad")
|
||||||
id = "trespasser"
|
id = "trespasser"
|
||||||
display_name = "Trespasser"
|
display_name = "Trespasser"
|
||||||
npc_type = 5
|
npc_type = 5
|
||||||
@@ -252,47 +155,18 @@ reactions_by_level = {
|
|||||||
7: 20
|
7: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
|
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["Meeting a friend inside. I'll only be a minute.", "Just cutting through."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_7vgx3"]
|
[sub_resource type="Resource" id="Resource_rivis"]
|
||||||
script = ExtResource("2_wfc6m")
|
script = ExtResource("2_l60ad")
|
||||||
id = "violent_threat"
|
id = "violent_threat"
|
||||||
display_name = "Violent Threat"
|
display_name = "Violent Threat"
|
||||||
npc_type = 6
|
npc_type = 6
|
||||||
temperament = 0.95
|
temperament = 0.95
|
||||||
base_compliance = 0.05
|
base_compliance = 0.05
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
2: 25,
|
2: 25,
|
||||||
@@ -320,66 +194,33 @@ reactions_by_level = {
|
|||||||
7: 30
|
7: 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
context_lines = {
|
||||||
1: {
|
"reason": ["Back off. This doesn't concern you."]
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String]([])
|
|
||||||
carried_items = Array[String]([])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_x43ed"]
|
[sub_resource type="Resource" id="Resource_1dvkp"]
|
||||||
script = ExtResource("2_wfc6m")
|
script = ExtResource("2_l60ad")
|
||||||
id = "authorized_staff"
|
id = "authorized_staff"
|
||||||
display_name = "Staff Member"
|
display_name = "Staff Member"
|
||||||
npc_type = 8
|
npc_type = 8
|
||||||
temperament = 0.15
|
temperament = 0.15
|
||||||
base_compliance = 1.0
|
base_compliance = 1.0
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
default_reactions = {
|
||||||
1: {
|
1: {
|
||||||
0: 100
|
0: 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reaction_delay = 0.6
|
context_lines = {
|
||||||
dialogue_lines = Array[String]([])
|
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
|
||||||
carried_items = Array[String]([])
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_07lsg"]
|
[sub_resource type="Resource" id="Resource_7wsm0"]
|
||||||
script = ExtResource("2_wfc6m")
|
script = ExtResource("2_l60ad")
|
||||||
id = "authorized_guest"
|
id = "authorized_guest"
|
||||||
display_name = "Authorized Guest"
|
display_name = "Authorized Guest"
|
||||||
npc_type = 9
|
npc_type = 9
|
||||||
temperament = 0.2
|
temperament = 0.2
|
||||||
base_compliance = 0.95
|
base_compliance = 0.95
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
default_reactions = {
|
||||||
1: {
|
1: {
|
||||||
0: 92,
|
0: 92,
|
||||||
@@ -390,16 +231,16 @@ default_reactions = {
|
|||||||
2: 12
|
2: 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reaction_delay = 0.6
|
context_lines = {
|
||||||
dialogue_lines = Array[String]([])
|
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
|
||||||
carried_items = Array[String]([])
|
}
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("3_8xm7a")
|
script = ExtResource("3_voh88")
|
||||||
id = "joke_shop"
|
id = "joke_shop"
|
||||||
display_name = "The Joke Shop"
|
display_name = "The Joke Shop"
|
||||||
description = "Comedy club door: IDs, reservations, and wand screening."
|
description = "Comedy club door: IDs, reservations, and wand screening."
|
||||||
access_policy = SubResource("Resource_xha77")
|
access_policy = SubResource("Resource_aal3l")
|
||||||
equipment = Array[String](["id_check", "guest_list", "wand", "radio"])
|
equipment = Array[String](["id_check", "guest_list", "wand", "radio"])
|
||||||
locations = {
|
locations = {
|
||||||
"door_area": Vector3(0, 0, 3),
|
"door_area": Vector3(0, 0, 3),
|
||||||
@@ -409,7 +250,7 @@ locations = {
|
|||||||
"sidewalk": Vector3(0, 0, 9),
|
"sidewalk": Vector3(0, 0, 9),
|
||||||
"steps_end": Vector3(0, 0, 3)
|
"steps_end": Vector3(0, 0, 3)
|
||||||
}
|
}
|
||||||
npc_profiles = Array[ExtResource("2_wfc6m")]([SubResource("Resource_8ksyk"), SubResource("Resource_wt1op"), SubResource("Resource_g1jr2"), SubResource("Resource_ys0b0"), SubResource("Resource_bv0jw"), SubResource("Resource_7vgx3"), SubResource("Resource_x43ed"), SubResource("Resource_07lsg")])
|
npc_profiles = Array[ExtResource("2_l60ad")]([SubResource("Resource_57xgi"), SubResource("Resource_r873e"), SubResource("Resource_3xy2u"), SubResource("Resource_50i1h"), SubResource("Resource_nsw77"), SubResource("Resource_20knl"), SubResource("Resource_rivis"), SubResource("Resource_1dvkp"), SubResource("Resource_7wsm0")])
|
||||||
event_pool = Array[Dictionary]([{
|
event_pool = Array[Dictionary]([{
|
||||||
"base_weight": 1.0,
|
"base_weight": 1.0,
|
||||||
"id": "guest_valid",
|
"id": "guest_valid",
|
||||||
@@ -564,6 +405,19 @@ event_pool = Array[Dictionary]([{
|
|||||||
"phase": 2,
|
"phase": 2,
|
||||||
"probability": 0.5,
|
"probability": 0.5,
|
||||||
"time_range_s": Vector2(650, 830)
|
"time_range_s": Vector2(650, 830)
|
||||||
|
}, {
|
||||||
|
"base_weight": 1.0,
|
||||||
|
"id": "joke_denied_guest_return",
|
||||||
|
"intent": "enter",
|
||||||
|
"kind": "recovery",
|
||||||
|
"npc_type": 9,
|
||||||
|
"override_access": {
|
||||||
|
"has_id": true,
|
||||||
|
"has_ticket": true
|
||||||
|
},
|
||||||
|
"phase": 2,
|
||||||
|
"probability": 0.2,
|
||||||
|
"time_range_s": Vector2(650, 830)
|
||||||
}])
|
}])
|
||||||
shift_phases = {
|
shift_phases = {
|
||||||
0: {
|
0: {
|
||||||
@@ -579,8 +433,6 @@ shift_phases = {
|
|||||||
"start_s": 700.0
|
"start_s": 700.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shift_duration_s = 900.0
|
|
||||||
policy_summary = ""
|
|
||||||
door_staff_lines = {
|
door_staff_lines = {
|
||||||
"id_check": ["May I see your ID, please?", "Could you show me some identification?"],
|
"id_check": ["May I see your ID, please?", "Could you show me some identification?"],
|
||||||
"prohibited_item": ["I can't allow that item inside. Please leave it outside.", "That item isn't permitted in the venue."],
|
"prohibited_item": ["I can't allow that item inside. Please leave it outside.", "That item isn't permitted in the venue."],
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[gd_resource type="Resource" script_class="VenueScenario" load_steps=17 format=3]
|
[gd_resource type="Resource" script_class="VenueScenario" format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://src/core/access_policy.gd" id="1_xxhx3"]
|
[ext_resource type="Script" path="res://src/core/access_policy.gd" id="1_3vwd0"]
|
||||||
[ext_resource type="Script" path="res://src/core/npc_profile.gd" id="2_fiebn"]
|
[ext_resource type="Script" path="res://src/core/npc_profile.gd" id="2_g7rkt"]
|
||||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_2dvuq"]
|
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_jd45y"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_blmt0"]
|
[sub_resource type="Resource" id="Resource_c55bb"]
|
||||||
script = ExtResource("1_xxhx3")
|
script = ExtResource("1_3vwd0")
|
||||||
required_artifacts = {
|
required_artifacts = {
|
||||||
1: true,
|
1: true,
|
||||||
3: true,
|
3: true,
|
||||||
@@ -13,145 +13,53 @@ required_artifacts = {
|
|||||||
5: true
|
5: true
|
||||||
}
|
}
|
||||||
grant_if_any_required = true
|
grant_if_any_required = true
|
||||||
minimum_age = 0
|
|
||||||
require_id = false
|
|
||||||
wand_screening_required = false
|
|
||||||
policy_summary = "Entry requires a wristband, credential, ticket, or guest-list confirmation."
|
policy_summary = "Entry requires a wristband, credential, ticket, or guest-list confirmation."
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_oddac"]
|
[sub_resource type="Resource" id="Resource_yyxim"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "tourist"
|
id = "tourist"
|
||||||
display_name = "Tourist"
|
display_name = "Tourist"
|
||||||
npc_type = 0
|
|
||||||
temperament = 0.1
|
temperament = 0.1
|
||||||
base_compliance = 0.9
|
base_compliance = 0.9
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
|
dialogue_lines = Array[String](["Oh sure, sorry, just taking a picture.", "Is there a better spot to stand?", "Understood, moving."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"deescalation": ["Okay, I'll move to the sidewalk.", "Sorry, I'll put the camera away."],
|
||||||
|
"reason": ["Just getting a photo of the entrance.", "I'm meeting friends here."],
|
||||||
|
"recognition": ["You remember me from the tour, right?"],
|
||||||
|
"refusal": ["I just want a photo, I'm not staying.", "I didn't know this entrance was restricted."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_byir2"]
|
[sub_resource type="Resource" id="Resource_lmn2c"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "confused_visitor"
|
id = "confused_visitor"
|
||||||
display_name = "Confused Visitor"
|
display_name = "Confused Visitor"
|
||||||
npc_type = 3
|
npc_type = 3
|
||||||
temperament = 0.2
|
temperament = 0.2
|
||||||
base_compliance = 0.85
|
base_compliance = 0.85
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
|
dialogue_lines = Array[String](["Wait, this isn't the entrance?", "Sorry about that, I'm a bit lost."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"deescalation": ["Oh, I understand. I'll check with the box office."],
|
||||||
|
"reason": ["I thought this was the box office.", "I'm looking for the event entrance."],
|
||||||
|
"refusal": ["I'm sure the event is here. Let me check my phone."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_3tt0b"]
|
[sub_resource type="Resource" id="Resource_if2cj"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "delivery"
|
id = "delivery"
|
||||||
display_name = "Delivery Worker"
|
display_name = "Delivery Worker"
|
||||||
npc_type = 10
|
npc_type = 10
|
||||||
temperament = 0.25
|
|
||||||
base_compliance = 0.8
|
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["Where should I leave this?"])
|
dialogue_lines = Array[String](["Where should I leave this?"])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["Drop-off for the venue. Where should I leave it?"]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_ihxib"]
|
[sub_resource type="Resource" id="Resource_t47d3"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "street_performer"
|
id = "street_performer"
|
||||||
display_name = "Street Performer"
|
display_name = "Street Performer"
|
||||||
npc_type = 1
|
npc_type = 1
|
||||||
temperament = 0.45
|
temperament = 0.45
|
||||||
base_compliance = 0.55
|
base_compliance = 0.55
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
0: 55,
|
0: 55,
|
||||||
@@ -159,47 +67,17 @@ reactions_by_level = {
|
|||||||
3: 30
|
3: 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
context_lines = {
|
||||||
1: {
|
"reason": ["Just doing my act; I'll keep the sidewalk clear."]
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String]([])
|
|
||||||
carried_items = Array[String]([])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_c57iy"]
|
[sub_resource type="Resource" id="Resource_j2mp5"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "influencer"
|
id = "influencer"
|
||||||
display_name = "Influencer"
|
display_name = "Influencer"
|
||||||
npc_type = 2
|
npc_type = 2
|
||||||
temperament = 0.5
|
temperament = 0.5
|
||||||
base_compliance = 0.5
|
base_compliance = 0.5
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
0: 40,
|
0: 40,
|
||||||
@@ -221,47 +99,18 @@ reactions_by_level = {
|
|||||||
8: 25
|
8: 25
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
|
dialogue_lines = Array[String](["This is public sidewalk, you can't stop me from filming."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["Filming a quick clip for my channel."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_bk410"]
|
[sub_resource type="Resource" id="Resource_uscqg"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "intoxicated"
|
id = "intoxicated"
|
||||||
display_name = "Intoxicated Pedestrian"
|
display_name = "Intoxicated Pedestrian"
|
||||||
npc_type = 4
|
npc_type = 4
|
||||||
temperament = 0.6
|
temperament = 0.6
|
||||||
base_compliance = 0.4
|
base_compliance = 0.4
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
0: 30,
|
0: 30,
|
||||||
@@ -275,41 +124,13 @@ reactions_by_level = {
|
|||||||
4: 10
|
4: 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 1.8
|
reaction_delay = 1.8
|
||||||
dialogue_lines = Array[String]([])
|
context_lines = {
|
||||||
carried_items = Array[String]([])
|
"reason": ["Looking for my friends. I think they went inside."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_jp52w"]
|
[sub_resource type="Resource" id="Resource_8aovb"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "trespasser"
|
id = "trespasser"
|
||||||
display_name = "Trespasser"
|
display_name = "Trespasser"
|
||||||
npc_type = 5
|
npc_type = 5
|
||||||
@@ -344,41 +165,13 @@ reactions_by_level = {
|
|||||||
7: 20
|
7: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
|
dialogue_lines = Array[String](["I'm just here to see a friend, come on.", "I'll be inside for ten seconds."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["Meeting a friend inside. I'll only be a minute.", "Just cutting through."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_5kplf"]
|
[sub_resource type="Resource" id="Resource_owl7s"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "mental_crisis"
|
id = "mental_crisis"
|
||||||
display_name = "Person in Crisis"
|
display_name = "Person in Crisis"
|
||||||
npc_type = 7
|
npc_type = 7
|
||||||
@@ -414,47 +207,18 @@ reactions_by_level = {
|
|||||||
7: 15
|
7: 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 2.4
|
reaction_delay = 2.4
|
||||||
dialogue_lines = Array[String]([])
|
context_lines = {
|
||||||
carried_items = Array[String]([])
|
"reason": ["I just need a quiet minute.", "I'm waiting for someone."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_6bscs"]
|
[sub_resource type="Resource" id="Resource_rnay4"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "violent_threat"
|
id = "violent_threat"
|
||||||
display_name = "Violent Threat"
|
display_name = "Violent Threat"
|
||||||
npc_type = 6
|
npc_type = 6
|
||||||
temperament = 0.95
|
temperament = 0.95
|
||||||
base_compliance = 0.05
|
base_compliance = 0.05
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {
|
reactions_by_level = {
|
||||||
1: {
|
1: {
|
||||||
2: 25,
|
2: 25,
|
||||||
@@ -482,41 +246,12 @@ reactions_by_level = {
|
|||||||
7: 30
|
7: 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
context_lines = {
|
||||||
1: {
|
"reason": ["Back off. This doesn't concern you."]
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String]([])
|
|
||||||
carried_items = Array[String]([])
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_n4lxh"]
|
[sub_resource type="Resource" id="Resource_lhp4m"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "protester"
|
id = "protester"
|
||||||
display_name = "Protester"
|
display_name = "Protester"
|
||||||
npc_type = 12
|
npc_type = 12
|
||||||
@@ -550,66 +285,34 @@ reactions_by_level = {
|
|||||||
7: 20
|
7: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default_reactions = {
|
|
||||||
1: {
|
|
||||||
0: 80,
|
|
||||||
2: 8,
|
|
||||||
3: 12
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
0: 70,
|
|
||||||
1: 10,
|
|
||||||
2: 12,
|
|
||||||
4: 8
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
0: 55,
|
|
||||||
1: 15,
|
|
||||||
2: 20,
|
|
||||||
4: 10
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
1: 65,
|
|
||||||
2: 15,
|
|
||||||
7: 20
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
1: 30,
|
|
||||||
5: 40,
|
|
||||||
7: 30
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reaction_delay = 0.6
|
|
||||||
dialogue_lines = Array[String](["I have every right to be on public sidewalk.", "You can't touch me, this is a lawful protest."])
|
dialogue_lines = Array[String](["I have every right to be on public sidewalk.", "You can't touch me, this is a lawful protest."])
|
||||||
carried_items = Array[String]([])
|
context_lines = {
|
||||||
|
"reason": ["We're demonstrating on the public sidewalk.", "This is a peaceful protest."]
|
||||||
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_6st6l"]
|
[sub_resource type="Resource" id="Resource_s8tjd"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "authorized_staff"
|
id = "authorized_staff"
|
||||||
display_name = "Staff Member"
|
display_name = "Staff Member"
|
||||||
npc_type = 8
|
npc_type = 8
|
||||||
temperament = 0.15
|
temperament = 0.15
|
||||||
base_compliance = 1.0
|
base_compliance = 1.0
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
default_reactions = {
|
||||||
1: {
|
1: {
|
||||||
0: 100
|
0: 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reaction_delay = 0.6
|
context_lines = {
|
||||||
dialogue_lines = Array[String]([])
|
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
|
||||||
carried_items = Array[String]([])
|
}
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_nf2oq"]
|
[sub_resource type="Resource" id="Resource_ii8kq"]
|
||||||
script = ExtResource("2_fiebn")
|
script = ExtResource("2_g7rkt")
|
||||||
id = "authorized_guest"
|
id = "authorized_guest"
|
||||||
display_name = "Authorized Guest"
|
display_name = "Authorized Guest"
|
||||||
npc_type = 9
|
npc_type = 9
|
||||||
temperament = 0.2
|
temperament = 0.2
|
||||||
base_compliance = 0.95
|
base_compliance = 0.95
|
||||||
return_probability = 0.0
|
|
||||||
reactions_by_level = {}
|
|
||||||
default_reactions = {
|
default_reactions = {
|
||||||
1: {
|
1: {
|
||||||
0: 92,
|
0: 92,
|
||||||
@@ -620,16 +323,16 @@ default_reactions = {
|
|||||||
2: 12
|
2: 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reaction_delay = 0.6
|
context_lines = {
|
||||||
dialogue_lines = Array[String]([])
|
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
|
||||||
carried_items = Array[String]([])
|
}
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("3_2dvuq")
|
script = ExtResource("3_jd45y")
|
||||||
id = "tv_studio"
|
id = "tv_studio"
|
||||||
display_name = "Hollywood Blvd Late Night TV Studio"
|
display_name = "Hollywood Blvd Late Night TV Studio"
|
||||||
description = "Crowd, celebrities, and staff around a late-night studio."
|
description = "Crowd, celebrities, and staff around a late-night studio."
|
||||||
access_policy = SubResource("Resource_blmt0")
|
access_policy = SubResource("Resource_c55bb")
|
||||||
equipment = Array[String](["wristband_policy", "guest_list", "radio", "flashlight"])
|
equipment = Array[String](["wristband_policy", "guest_list", "radio", "flashlight"])
|
||||||
locations = {
|
locations = {
|
||||||
"entrance": Vector3(0, 0, 1),
|
"entrance": Vector3(0, 0, 1),
|
||||||
@@ -639,7 +342,7 @@ locations = {
|
|||||||
"stage_door": Vector3(6, 0, -2),
|
"stage_door": Vector3(6, 0, -2),
|
||||||
"steps_end": Vector3(0, 0, 3)
|
"steps_end": Vector3(0, 0, 3)
|
||||||
}
|
}
|
||||||
npc_profiles = Array[ExtResource("2_fiebn")]([SubResource("Resource_oddac"), SubResource("Resource_byir2"), SubResource("Resource_3tt0b"), SubResource("Resource_ihxib"), SubResource("Resource_c57iy"), SubResource("Resource_bk410"), SubResource("Resource_jp52w"), SubResource("Resource_5kplf"), SubResource("Resource_6bscs"), SubResource("Resource_n4lxh"), SubResource("Resource_6st6l"), SubResource("Resource_nf2oq")])
|
npc_profiles = Array[ExtResource("2_g7rkt")]([SubResource("Resource_yyxim"), SubResource("Resource_lmn2c"), SubResource("Resource_if2cj"), SubResource("Resource_t47d3"), SubResource("Resource_j2mp5"), SubResource("Resource_uscqg"), SubResource("Resource_8aovb"), SubResource("Resource_owl7s"), SubResource("Resource_rnay4"), SubResource("Resource_lhp4m"), SubResource("Resource_s8tjd"), SubResource("Resource_ii8kq")])
|
||||||
event_pool = Array[Dictionary]([{
|
event_pool = Array[Dictionary]([{
|
||||||
"base_weight": 1.0,
|
"base_weight": 1.0,
|
||||||
"id": "audience_ticket",
|
"id": "audience_ticket",
|
||||||
@@ -790,6 +493,15 @@ event_pool = Array[Dictionary]([{
|
|||||||
"phase": 2,
|
"phase": 2,
|
||||||
"probability": 0.45,
|
"probability": 0.45,
|
||||||
"time_range_s": Vector2(700, 860)
|
"time_range_s": Vector2(700, 860)
|
||||||
|
}, {
|
||||||
|
"base_weight": 1.0,
|
||||||
|
"id": "studio_manager_check",
|
||||||
|
"intent": "enter",
|
||||||
|
"kind": "recovery",
|
||||||
|
"npc_type": 8,
|
||||||
|
"phase": 2,
|
||||||
|
"probability": 0.2,
|
||||||
|
"time_range_s": Vector2(620, 820)
|
||||||
}])
|
}])
|
||||||
shift_phases = {
|
shift_phases = {
|
||||||
0: {
|
0: {
|
||||||
@@ -805,6 +517,6 @@ shift_phases = {
|
|||||||
"start_s": 700.0
|
"start_s": 700.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shift_duration_s = 900.0
|
door_staff_lines = {
|
||||||
policy_summary = ""
|
"ticket_check": ["Ticket, credential, or guest-list confirmation, please.", "I need your ticket or guest-list confirmation."]
|
||||||
door_staff_lines = {}
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
# 3D model review (2026-11-03)
|
||||||
|
|
||||||
|
Status: **baseline measured; scorecard open.** This is the working document for the unversioned
|
||||||
|
`3D model re-evaluation` track in [`TODO.md`](../TODO.md). It records what was measured, what the
|
||||||
|
Blender skill guidance says the targets are, the ranked candidate gaps, and the decisions that
|
||||||
|
still need the project owner. Nothing here re-opens the accepted 0.9.4 limits by itself.
|
||||||
|
|
||||||
|
Related documents: [`npc-visual-review.md`](npc-visual-review.md) (accepted 0.9.4 limits and
|
||||||
|
before/after sheets), [`npc-proportion-audit.md`](npc-proportion-audit.md) (proportion guide),
|
||||||
|
[`blender-assets.md`](blender-assets.md) (asset contract), [`asset-tooling.md`](asset-tooling.md)
|
||||||
|
(commands).
|
||||||
|
|
||||||
|
## Method and skill routing
|
||||||
|
|
||||||
|
The review is screenshot- and measurement-backed, following `qa-review` (screenshot set, defect
|
||||||
|
severity, explicit verdict) rather than an opinion pass:
|
||||||
|
|
||||||
|
| Family | Skills loaded | Criteria taken from them |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Routing and pipeline | `blender-director` | Plan → blockout → detail → validate → export; MCP steps translated to headless flatpak runs |
|
||||||
|
| Characters | `character-artist`, `retopology`, `rigging` | Eye/mouth/jaw/shoulder loops, quad-dominant deformation surfaces, 3-loop joints, no triangles on primary deformers |
|
||||||
|
| UVs and textures | `uv-workflow`, `texture-workflow`, `materials` | Texel-density tiers (hero 512–1024, standard 256–512, background 128–256 px/m), consistent density, no overlapping UVs |
|
||||||
|
| Budgets and structure | `asset-optimization`, `lod-pipeline` | Triangle/vertex budgets, naming, LOD tiers |
|
||||||
|
| Props, gear, foliage | `prop-artist`, `hard-surface`, `vegetation-artist` | Silhouette read, bevel/chamfer language, collision proxies, carded foliage |
|
||||||
|
| Look and verdict | `lookdev`, `qa-review` | Day/night beauty captures, ship / ship-with-notes / no-ship verdict |
|
||||||
|
|
||||||
|
Project contract and gates stay authoritative; see `AGENTS.md` §Local Blender skills. The skills
|
||||||
|
assume Blender MCP, which this project does not use: everything below came from the headless
|
||||||
|
`tools/blender_assets.py` / `tools/npc_pipeline.py` pipelines plus the Godot capture harnesses.
|
||||||
|
|
||||||
|
## What was captured and measured (2026-11-03)
|
||||||
|
|
||||||
|
| Evidence | Command | Result |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Contract audit | `python3 tools/blender_assets.py audit --json` | 46/46 assets clean; library 59,012 tris |
|
||||||
|
| Imported-GLB inspection | `blender_assets.py inspect` (all 46) | All clean; transforms applied, helpers ignored |
|
||||||
|
| Gear fit | `python3 tools/blender_assets.py fit --json` | Head width 0.315 m on all eight; clothing radius 0.190–0.260 m |
|
||||||
|
| Proportions | `python3 tools/npc_proportions.py report --json` | All eight inside the stylized guide |
|
||||||
|
| Texel density | `python3 tools/blender_assets.py texels --json` | New measurement, table below |
|
||||||
|
| Neutral baseline | `capture_npc_baseline.gd` → `tmp/model-baseline` (48 frames) + `npc_baseline.py sheet` | 48 frames, none blank; sheet in `tmp/` only |
|
||||||
|
| Final context | `capture_npc_review.gd` → `tmp/model-review` (96 frames) | 96 frames, none blank; raw frames stay local |
|
||||||
|
| Performance | `smoke_npc_budget.gd` | 24 avatars, 168 meshes, 131,616 tris, 6.94 ms of 50 ms |
|
||||||
|
|
||||||
|
Raw captures and JSON stay in the gitignored `tmp/`; committed review images remain the
|
||||||
|
`docs/blender_previews/sheet_*.jpg` set from 0.9.4/0.9.5 until a fix actually lands.
|
||||||
|
|
||||||
|
## Character measurements
|
||||||
|
|
||||||
|
Contract numbers from `audit` + `inspect`; density from `texels` (256² slot maps, authored and
|
||||||
|
with the runtime 4× fabric repeat).
|
||||||
|
|
||||||
|
| Character | Tris / verts | Height / feet | Materials | Authored td (px/m) | Fabric effective (px/m) | Verdict |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| `npc_casual` | 4,482 / 4,260 | 1.824 / +0.000 | 7 | 106.8–122.2 | 429–440 | Contract pass; visual accepted 0.9.4 |
|
||||||
|
| `npc_jacket` | 4,872 / 4,662 | 1.824 / +0.000 | 7 | 100.3–113.7 | 409–413 | Contract pass; visual accepted 0.9.4 |
|
||||||
|
| `npc_coat` | 5,474 / 5,265 | 1.824 / +0.000 | 7 | 75.3–81.4 | 307–310 | Contract pass; visual accepted 0.9.4 |
|
||||||
|
| `npc_vest` | 4,980 / 4,879 | 1.819 / +0.000 | 9 | 85.5–99.3 | 362–366 | Contract pass; visual accepted 0.9.4 |
|
||||||
|
| `npc_dress` | 4,900 / 4,719 | 1.825 / +0.000 | 7 | 81.9–89.0 | 338–339 | Contract pass; visual accepted 0.9.4 |
|
||||||
|
| `npc_hoodie` | 5,304 / 4,950 | 1.856 / +0.000 | 7 | 89.5–98.6 | 359–364 | Contract pass; visual accepted 0.9.4 |
|
||||||
|
| `npc_firefighter` | 5,344 / 5,494 | 1.819 / +0.000 | 9 | 64.0–75.1 | 287–290 | Contract pass; lowest density |
|
||||||
|
| `npc_guard` | 5,046 / 5,096 | 1.819 / +0.000 | 11 | 90.0–100.8 | 374–374 | Contract pass; visual accepted 0.9.4 |
|
||||||
|
| `npc_base` (fallback) | 1,204 / 2,488 | 1.836 / +0.000 | 5 | 70.1–72.9 | 4× on fabric | Contract pass; far below its 6,000/4,000 cap |
|
||||||
|
|
||||||
|
Blender-side `inspect` counts can differ by ±1 vertex from the glTF accessor count in `audit`
|
||||||
|
(normal-split rounding); both are recorded and neither changes a verdict.
|
||||||
|
|
||||||
|
Readings:
|
||||||
|
|
||||||
|
- Every character is inside 8,000/8,000 with the farthest at 5,474 tris (68% of budget) and 5,494
|
||||||
|
verts (69%), so there is headroom for targeted topology detail without a cap change.
|
||||||
|
- Surface area tracks clothing volume: the firefighter coat is the largest body (4.28 m²) and has
|
||||||
|
the lowest authored density; `npc_casual` is the smallest (2.44 m²) and the highest.
|
||||||
|
- **Authored density (64–122 px/m) sits below the skill's background tier (128–256 px/m).** The
|
||||||
|
runtime tint controls most of the look: `skin`, `hair`, `accessory` and the fixed detail slots
|
||||||
|
are neutral maps tinted at runtime, not painted skin, so density mostly controls subtle
|
||||||
|
weave/noise. Fabric is effective 287–440 px/m (standard tier) because of the 4× repeat. This is
|
||||||
|
a recorded note, not a defect, until a portrait shows texture starvation.
|
||||||
|
- Material counts 7–11 per character are fixed detail slots (eyes, rubber, hivis, reflective,
|
||||||
|
leather, metal), not extra tint slots.
|
||||||
|
|
||||||
|
## Scorecard skeleton
|
||||||
|
|
||||||
|
Verdicts use `qa-review` semantics: **P0** (blocker: wrong scale, broken normals, budget fail),
|
||||||
|
**P1** (major: silhouette/readability), **P2** (minor), **accept** (recorded limit). `—` means not
|
||||||
|
yet reviewed; this document is the place to fill it.
|
||||||
|
|
||||||
|
### Characters
|
||||||
|
|
||||||
|
| Character | Silhouette | Face @1.85 m | Hands/grip | Weights/pose | Cloth/hair | LOD | Verdict |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| `npc_casual` | accept (0.9.4) | P1 (faceted planes) | accept (open palm) | — | accept | accept (two-quad impostor) | pending owner |
|
||||||
|
| `npc_jacket` | accept | P1 | accept | — | accept | accept | pending owner |
|
||||||
|
| `npc_coat` | accept | P1 | accept | — | accept (hem 0.76 stride) | accept | pending owner |
|
||||||
|
| `npc_vest` | accept | P1 | accept | — | accept | accept | pending owner |
|
||||||
|
| `npc_dress` | accept; performer props dominate | P1 | accept | — | accept | accept | pending owner |
|
||||||
|
| `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | — | accept | accept | pending owner |
|
||||||
|
| `npc_firefighter` | accept | P1 | accept | — | accept | accept | pending owner |
|
||||||
|
| `npc_guard` | accept | P1 | accept | — | accept | accept | pending owner |
|
||||||
|
|
||||||
|
### Gear (18)
|
||||||
|
|
||||||
|
| Gear | Attach | Tris | Verdict |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `cap` | head | 328 | — (fit measured, visual pending) |
|
||||||
|
| `beanie` | head | 460 | — (unused by any character) |
|
||||||
|
| `hood` | head | 804 | — |
|
||||||
|
| `sunglasses` | head | 164 | accept (0.9.4 clearance) |
|
||||||
|
| `firefighter_helmet` | head | 672 | accept (0.9.4 + 3 cm runtime offset) |
|
||||||
|
| `backpack` | back | 380 | accept (0.9.4 strap refit) |
|
||||||
|
| `shoulder_bag` | back | 188 | — |
|
||||||
|
| `scba_tank` | back | 362 | — |
|
||||||
|
| `lanyard` | chest | 100 | accept (0.9.4 + 4 cm runtime offset) |
|
||||||
|
| `phone` | hand | 96 | accept (forward offset) |
|
||||||
|
| `cup` | hand | 188 | accept |
|
||||||
|
| `clipboard` | hand | 84 | accept |
|
||||||
|
| `umbrella` | hand | 134 | accept |
|
||||||
|
| `camera` | hand | 318 | accept |
|
||||||
|
| `protest_sign` | hand | 92 | accept (performer/protester read) |
|
||||||
|
| `flashlight` | hand | 168 | accept |
|
||||||
|
| `wand` | hand | 244 | accept |
|
||||||
|
| `radio` | hand | 220 | accept |
|
||||||
|
|
||||||
|
Gear total: 5,002 tris across 18 pieces (all far below the 1,500-per-piece cap).
|
||||||
|
|
||||||
|
### Props (20 library + 6 generated fallbacks)
|
||||||
|
|
||||||
|
Library total 13,608 tris; `marquee` (2,804) and `street_tree` (2,592) are the two heaviest and are
|
||||||
|
the first candidates for a `vegetation-artist`/lookdev pass.
|
||||||
|
|
||||||
|
| Prop | Tris | Verdict |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `bollard` | 138 | — |
|
||||||
|
| `bench` | 560 | — |
|
||||||
|
| `trash_bin` | 228 | — |
|
||||||
|
| `planter_box` | 1,324 | — |
|
||||||
|
| `newspaper_box` | 244 | — |
|
||||||
|
| `fire_hydrant` | 466 | — |
|
||||||
|
| `street_lamp` | 428 | — |
|
||||||
|
| `street_tree` | 2,592 | P1 candidate (flat-shaded clumps) |
|
||||||
|
| `palm_tree` | 400 | P1 candidate (same foliage language) |
|
||||||
|
| `round_planter` | 1,240 | — |
|
||||||
|
| `a_frame_sign` | 212 | — |
|
||||||
|
| `wall_lantern` | 180 | — |
|
||||||
|
| `traffic_cone` | 210 | — |
|
||||||
|
| `crowd_barrier` | 388 | — |
|
||||||
|
| `stanchion` | 434 | — |
|
||||||
|
| `handrail` | 268 | — |
|
||||||
|
| `security_podium` | 184 | — |
|
||||||
|
| `double_door` | 380 | — |
|
||||||
|
| `marquee` | 2,804 | — (emissive check pending) |
|
||||||
|
| `sedan` | 928 | — |
|
||||||
|
|
||||||
|
Generated fallbacks (`assets/models/generated/`): bench, bollard, fire_hydrant, newspaper_box,
|
||||||
|
planter_box, trash_bin. They are only used when the library prop is missing, so they are audited
|
||||||
|
for presence, not re-reviewed for art quality.
|
||||||
|
|
||||||
|
### Venues
|
||||||
|
|
||||||
|
| Venue | Measured (map-budget test) | Status |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Hollywood Boulevard | 58,884 tris / 409 meshes | shape-language review pending |
|
||||||
|
| The Joke Shop | 22,680 tris / 454 meshes | shape-language review pending |
|
||||||
|
| TV Studio | — | shape-language review pending |
|
||||||
|
|
||||||
|
## Ranked candidate gaps
|
||||||
|
|
||||||
|
Ordered by expected player-visible gain per unit of work. None is approved for fixing yet; the
|
||||||
|
owner review decides.
|
||||||
|
|
||||||
|
1. **Face plane structure at portrait distance (P1, all eight).** The neutral sheet shows flat,
|
||||||
|
faceted cheek/brow planes and slit-like eyes at 1.85 m. `character-artist`/`retopology` guidance
|
||||||
|
asks for concentric eye loops and a 3-loop mouth. This is the largest readability gap and also
|
||||||
|
the most expensive (head rebuild + re-UV + re-AO per variant). Needs a targeted single-variant
|
||||||
|
prototype before committing to eight.
|
||||||
|
2. **No pose/weight evidence (unknown).** Idle, walk, sit, block and turn deformation has never been
|
||||||
|
captured as a review set, so shoulder/hip/neck collapse is unmeasured. Cheap to add and it
|
||||||
|
gates any claim that the rig is acceptable.
|
||||||
|
3. **Foliage language (P1, `street_tree`, `palm_tree`, `planter_box`, `round_planter`).** Flat-shaded
|
||||||
|
clumps against an otherwise readable set, already deferred once. `vegetation-artist` carded
|
||||||
|
clusters would fix it inside the existing triangle budget.
|
||||||
|
4. **Texel density below the background tier (note).** 64–122 px/m authored vs the skill's 128–256
|
||||||
|
background tier. Fabric is already standard-tier at runtime. Decide: accept because the maps are
|
||||||
|
neutral tinted detail, or raise only `skin`/`hair` to 512² for portrait distance.
|
||||||
|
5. **`npc_firefighter` density floor (P2).** 64 px/m with the largest coat area; split the coat UV
|
||||||
|
shell or raise the map size before the next character pass, whichever the density decision says.
|
||||||
|
6. **Hands at conversation distance (accept, revisit only if face work opens the budget).** Open
|
||||||
|
palm with `HAND_ITEM_FORWARD`; no finger bones exist, so a curl rig is a contract decision, not
|
||||||
|
a modelling tidy-up.
|
||||||
|
7. **Gear never reviewed as silhouette language (P2).** 18 pieces pass fit/contract but have no
|
||||||
|
prop-artist verdict; `beanie` is unused and could be retired or reassigned.
|
||||||
|
8. **Venue shape language (P2).** Hollywood and the Joke Shop have budget data but no
|
||||||
|
`environment-artist`/`archviz` review; the TV Studio has no recorded map-budget number in this
|
||||||
|
document yet.
|
||||||
|
|
||||||
|
## Decisions needed from the owner
|
||||||
|
|
||||||
|
- Accept the measured texel density as a stylized limitation, or fund a skin/hair resolution pass.
|
||||||
|
- Approve or reject a single-variant face/eye-loop prototype (and pick which variant to prototype
|
||||||
|
on) before any eight-character rebuild.
|
||||||
|
- Decide whether the pose/weight capture is required before the crowd-variant roster.
|
||||||
|
- Confirm foliage rebuild priority versus the map track (both touch the same street trees).
|
||||||
|
|
||||||
|
## Next actions
|
||||||
|
|
||||||
|
Tracked in [`TODO.md`](../TODO.md) `3D model re-evaluation`: the remaining `A. Baseline…` item is
|
||||||
|
this owner review; `B`/`C`/`D` carry the per-family work, and `E` adds the pose/weight,
|
||||||
|
collider-coverage and scorecard-coverage gates this document found missing.
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Gig Line Documentation
|
||||||
|
|
||||||
|
This folder holds the project documentation, visual references and generated QA evidence.
|
||||||
|
|
||||||
|
## Current priorities
|
||||||
|
|
||||||
|
1. [Character development](../TODO.md#character-development) — crowd, age, build, hair and outfit variety; generator-first, with the frozen NPC contract.
|
||||||
|
2. [Local map development](../TODO.md#local-map-development) — OpenStreetMap/3D data, licensed reference imagery, photoreal facades, AI-assisted composition and Gaussian-splatting evaluation.
|
||||||
|
3. [0.10.0 release candidate](../TODO.md#milestone-0100--release-candidate) — versioned release hardening after the unversioned development tracks.
|
||||||
|
|
||||||
|
## Start here
|
||||||
|
|
||||||
|
- [`../PLAN.md`](../PLAN.md) — product, design intent and development priorities
|
||||||
|
- [`../TODO.md`](../TODO.md) — priority-ordered active tasks and roadmap
|
||||||
|
- [`npc-model-sourcing.md`](npc-model-sourcing.md) — character-source evaluation, licence rules and the character-development plan
|
||||||
|
- [`architecture.md`](architecture.md) — arc42 system architecture
|
||||||
|
- [`getting-started.md`](getting-started.md) — setup, run/test commands and controls
|
||||||
|
- [`asset-tooling.md`](asset-tooling.md) — image/model pipelines and screenshot gates
|
||||||
|
- [`gameplay.md`](gameplay.md) — controls, venues and escalation
|
||||||
|
- [`joke-shop-map.md`](joke-shop-map.md) — Sunset Strip real-site mapping
|
||||||
|
- [`tutorial.md`](tutorial.md) — first-shift tutorial beats
|
||||||
|
- [`npc-visual-review.md`](npc-visual-review.md) — NPC visual baseline, acceptance criteria and ranked gaps
|
||||||
|
- [`npc-proportion-audit.md`](npc-proportion-audit.md) — NPC proportion ratios and stylized guide results
|
||||||
|
- [`3d-model-review.md`](3d-model-review.md) — 3D model re-evaluation: measurements, scorecard and ranked gaps
|
||||||
|
- [`development-status.md`](development-status.md) — implemented features, current gaps and active planning
|
||||||
|
- [`accessibility.md`](accessibility.md) — accessibility statement and known gaps
|
||||||
|
- [`known-limitations.md`](known-limitations.md) — release-candidate blockers and accepted limits
|
||||||
|
- [`smoke-checklist.md`](smoke-checklist.md) — manual verification
|
||||||
|
- [`balance-acceptance.md`](balance-acceptance.md) — release-candidate balance targets and evidence protocol
|
||||||
|
- [`../CREDITS.md`](../CREDITS.md) — credits and asset provenance
|
||||||
|
- [`../PRIVACY.md`](../PRIVACY.md) — privacy note
|
||||||
|
- [`ui-copy.md`](ui-copy.md) — screen, copy and button inventory
|
||||||
|
- [`RELEASING.md`](RELEASING.md) — export, CI and publishing workflow
|
||||||
|
- [`adr/README.md`](adr/README.md) — architecture decisions
|
||||||
|
|
||||||
|
## Visual references
|
||||||
|
|
||||||
|
- [`screens/README.md`](screens/README.md) — concepts, designs, screenshots and realism runs
|
||||||
|
- [`blender_previews/README.md`](blender_previews/README.md) — character/gear/prop previews
|
||||||
|
- [`screenshots/README.md`](screenshots/README.md) — committed game gallery
|
||||||
|
- [`screen-design-report.md`](screen-design-report.md) — latest screen-design judge result
|
||||||
|
|
||||||
|
## Quick preview
|
||||||
|
|
||||||
|
| Reference | Screenshot |
|
||||||
|
| --- | --- |
|
||||||
|
| Venue design | <img src="screens/designs/venue_overview.png" width="320" alt="Venue overview design"> |
|
||||||
|
| Current entrance | <img src="screenshots/01-venue-overview.png" width="320" alt="In-game venue overview"> |
|
||||||
|
| Character sheet | <img src="blender_previews/sheet_npc.jpg" width="320" alt="NPC character sheet"> |
|
||||||
|
| Prop sheet | <img src="blender_previews/sheet_props.jpg" width="320" alt="Prop asset sheet"> |
|
||||||
@@ -12,11 +12,11 @@ time you export, either:
|
|||||||
matching templates), or
|
matching templates), or
|
||||||
- Install them manually from the
|
- Install them manually from the
|
||||||
[Godot downloads](https://godotengine.org/download/) page by placing the
|
[Godot downloads](https://godotengine.org/download/) page by placing the
|
||||||
`Godot_v4.3-stable_export_templates.tpz` contents into
|
`Godot_v4.7.2-stable_export_templates.tpz` contents into
|
||||||
`~/.local/share/godot/export_templates/4.3.stable/`.
|
`~/.local/share/godot/export_templates/4.7.2.stable/`.
|
||||||
|
|
||||||
The templates must match the editor binary you use locally. CI pins Godot 4.3;
|
The templates must match the editor binary you use locally. CI and local
|
||||||
the local development machine uses 4.7.2 with its matching templates. Verify with:
|
development both use Godot 4.7.2 with its matching templates. Verify with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
godot --headless --export-release "Linux" build/gig-line-smoke.x86_64
|
godot --headless --export-release "Linux" build/gig-line-smoke.x86_64
|
||||||
@@ -27,11 +27,17 @@ godot --headless --export-release "Linux" build/gig-line-smoke.x86_64
|
|||||||
`project.godot` sets `config/version`. Keep it in sync with the release tag
|
`project.godot` sets `config/version`. Keep it in sync with the release tag
|
||||||
(e.g. `v0.3.0` → `config/version="0.3.0"`, plus a git tag `v0.3.0`).
|
(e.g. `v0.3.0` → `config/version="0.3.0"`, plus a git tag `v0.3.0`).
|
||||||
|
|
||||||
|
Every major, minor and patch increment is a version in its own right: add the
|
||||||
|
`CHANGELOG.md` entry, run the local build (`tools/build_releases.sh`), verify the
|
||||||
|
artifact names/sizes/checksums, and only then commit and tag. A changelog entry
|
||||||
|
without a matching build is not a completed version. Unversioned development tracks
|
||||||
|
do not get a placeholder version; assign one when the scope enters a release.
|
||||||
|
|
||||||
## 3. CI (Gitea Actions)
|
## 3. CI (Gitea Actions)
|
||||||
|
|
||||||
The repository ships a Gitea Actions workflow (`.gitea/workflows/build.yml`):
|
The repository ships a Gitea Actions workflow (`.gitea/workflows/build.yml`):
|
||||||
|
|
||||||
- Every push/PR runs the **checks** job: Godot 4.3 install, project import, and the
|
- Every push/PR runs the **checks** job: Godot 4.7.2 install, project import, and the
|
||||||
full headless test suite. This is the fast safety net.
|
full headless test suite. This is the fast safety net.
|
||||||
- **Tags (`v*`)** and **manual `workflow_dispatch`** runs additionally execute the
|
- **Tags (`v*`)** and **manual `workflow_dispatch`** runs additionally execute the
|
||||||
**build** job: export templates install, import, `tools/build_releases.sh` for
|
**build** job: export templates install, import, `tools/build_releases.sh` for
|
||||||
@@ -63,10 +69,12 @@ setup, so do not rely on it.
|
|||||||
> artifacts. If CI publishing fails again, run
|
> artifacts. If CI publishing fails again, run
|
||||||
> `./tools/publish_release.sh <version>` from a checkout with `~/.git-credentials`.
|
> `./tools/publish_release.sh <version>` from a checkout with `~/.git-credentials`.
|
||||||
|
|
||||||
> **No `actions/cache` for now:** the runner has no local actions-cache service, so
|
> **Godot cache evaluation:** Gitea Actions cache was tested for the Godot binary and
|
||||||
> `actions/cache` times out against GitHub's cache API. We dropped it (Godot import
|
> trimmed 4.7.2 templates. Restores worked intermittently, but cache saves timed out
|
||||||
> is only ~4s). If a host admin later enables Gitea `[cache.actions]` with a cache
|
> in `CreateCacheEntry` / were rejected as reserved by another job. The cache steps
|
||||||
> server, the step can be reintroduced.
|
> were removed because they added restore/save overhead without a reliable hit.
|
||||||
|
> Revisit when the host's Gitea Actions cache service is healthy; otherwise keep
|
||||||
|
> downloading the binary/templates directly.
|
||||||
|
|
||||||
> The workflow lives in `.gitea/workflows/` (Gitea's canonical path). The old
|
> The workflow lives in `.gitea/workflows/` (Gitea's canonical path). The old
|
||||||
> `.github/workflows/ci.yml` was removed to avoid double runs on hosts that scan
|
> `.github/workflows/ci.yml` was removed to avoid double runs on hosts that scan
|
||||||
@@ -101,6 +109,31 @@ The optional Desktop HQ zip is assembled by `tools/build_releases.sh` from
|
|||||||
`packaging/desktop_hq/`; keep `override.cfg` next to the extracted executable.
|
`packaging/desktop_hq/`; keep `override.cfg` next to the extracted executable.
|
||||||
It is not part of the default Compatibility/Web build.
|
It is not part of the default Compatibility/Web build.
|
||||||
|
|
||||||
|
`tools/publish_release.sh` preflights every artifact for existence and non-zero
|
||||||
|
size, checks free space in `build/`, writes `build/SHA256SUMS-<version>.txt`, and
|
||||||
|
uploads only after those checks pass. If an upload or the final asset-count check
|
||||||
|
fails, it removes the assets it just uploaded (and removes a newly created
|
||||||
|
release) so the host never keeps a partial release. Use
|
||||||
|
`PUBLISH_DRY_RUN=1 ./tools/publish_release.sh` to run the preflight and checksum
|
||||||
|
steps without touching the API. Set `RETAIN_RELEASES=7` to prune older releases
|
||||||
|
after a successful publish; the default `0` disables pruning and never deletes
|
||||||
|
history implicitly. A retention of seven keeps the newest seven releases,
|
||||||
|
including the latest 0.8/0.9 line.
|
||||||
|
|
||||||
|
### 0.10.0 macOS signing decision
|
||||||
|
|
||||||
|
The 0.10.0 release candidate will continue to ship an **unsigned** macOS zip with
|
||||||
|
the documented right-click/`xattr` quarantine instructions. Signing and
|
||||||
|
notarization remain a public-release blocker until an Apple Developer account and
|
||||||
|
a reproducible signing/notarization job are available; they are not silently
|
||||||
|
claimed as complete. The same limitation is recorded in
|
||||||
|
[`known-limitations.md`](known-limitations.md).
|
||||||
|
|
||||||
|
Always run the release build from a clean checkout or detached worktree. Godot
|
||||||
|
can pack files from ignored `tmp/` and old `build/` folders into the PCK; the
|
||||||
|
0.9.6 dirty-tree build produced 1.6–3.1 GB artifacts, while the clean worktree
|
||||||
|
produced the expected ~160–240 MB packages.
|
||||||
|
|
||||||
Locally you can build the AppImage with `bash packaging/linux/build_appimage.sh <version>`;
|
Locally you can build the AppImage with `bash packaging/linux/build_appimage.sh <version>`;
|
||||||
the NSIS installer requires `makensis` (covered by CI).
|
the NSIS installer requires `makensis` (covered by CI).
|
||||||
|
|
||||||
@@ -127,14 +160,15 @@ published the release, re-running the script only adds missing assets.
|
|||||||
|
|
||||||
> **Host note**: the release is fully built by CI, but anonymous users can only
|
> **Host note**: the release is fully built by CI, but anonymous users can only
|
||||||
> browse and download when the instance sets `[service] REQUIRE_SIGNIN_VIEW=false`
|
> browse and download when the instance sets `[service] REQUIRE_SIGNIN_VIEW=false`
|
||||||
> in `app.ini` (the repo itself is public). See the TODO for the exact steps.
|
> in `app.ini` (the repo itself is public). This is a host-admin setting, not a
|
||||||
|
> repository file.
|
||||||
|
|
||||||
Manual alternative:
|
Manual alternative:
|
||||||
|
|
||||||
### Via the web UI
|
### Via the web UI
|
||||||
|
|
||||||
1. Create a **New Release** with tag `v<version>`.
|
1. Create a **New Release** with tag `v<version>`.
|
||||||
2. Attach all three `build/` artifacts.
|
2. Attach all artifacts from `build/` (portables, installer/AppImage, Web and optional Desktop HQ).
|
||||||
3. Write release notes (see checklist below).
|
3. Write release notes (see checklist below).
|
||||||
|
|
||||||
### Via the API
|
### Via the API
|
||||||
@@ -171,7 +205,7 @@ change, update `tools/build_releases.sh` first.
|
|||||||
|
|
||||||
- [ ] All headless tests pass (exit code 0).
|
- [ ] All headless tests pass (exit code 0).
|
||||||
- [ ] Game boots cleanly in a smoke test.
|
- [ ] Game boots cleanly in a smoke test.
|
||||||
- [ ] Controls documented in `README.md` match the build.
|
- [ ] Controls documented in `docs/getting-started.md` match the build.
|
||||||
- [ ] Version in `project.godot` matches the tag.
|
- [ ] Version in `project.godot` matches the tag.
|
||||||
- [ ] Export templates used are the official ones for the pinned engine.
|
- [ ] Export templates used are the official ones for the pinned engine.
|
||||||
- [ ] Artifacts uploaded; README downloads page shows them.
|
- [ ] Artifacts uploaded; README downloads page shows them.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Accessibility Statement
|
||||||
|
|
||||||
|
Gig Line is a keyboard/mouse-first first-person game with gamepad support. The
|
||||||
|
current build includes the following accessibility features:
|
||||||
|
|
||||||
|
- Remappable keyboard and gamepad bindings, persisted in `settings.json`.
|
||||||
|
- Keyboard focus rings and explicit default focus in every modal, with A/B
|
||||||
|
accept/cancel on a gamepad and wrapped Tab/arrow navigation.
|
||||||
|
- Adjustable HUD font scale up to 1.75× with clipped/ellipsized long labels.
|
||||||
|
- Reduced-motion mode that disables head bob and motion-sensitive presentation.
|
||||||
|
- Captions in the HUD log for radio, interaction and event messages.
|
||||||
|
- Master, SFX and ambience volume controls.
|
||||||
|
- Shape plus number escalation meter, so escalation is readable without colour.
|
||||||
|
- Hold-to-confirm emergency action and guarded destructive actions.
|
||||||
|
- Searchable Help generated from the live input bindings.
|
||||||
|
- Mouse-transparent HUD, right-click/Esc modal handling, and a predictable
|
||||||
|
top-panel close path.
|
||||||
|
|
||||||
|
Known gaps:
|
||||||
|
|
||||||
|
- There is no screen-reader integration or text-to-speech narration.
|
||||||
|
- The game is English-only; no pseudo-locale or full localization review has
|
||||||
|
been completed.
|
||||||
|
- The visual style is stylized and some small environmental detail may be hard
|
||||||
|
to read at low resolution; a full low-vision/high-contrast audit is pending.
|
||||||
|
- Audio captions cover gameplay messages, not every ambient sound.
|
||||||
|
- A formal WCAG conformance claim has **not** been made. External accessibility
|
||||||
|
review is still a 0.10.0 release-review item.
|
||||||
|
|
||||||
|
Test evidence and the manual checklist live in
|
||||||
|
[`smoke-checklist.md`](smoke-checklist.md); settings and save behaviour are
|
||||||
|
documented in [`getting-started.md`](getting-started.md).
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# ADR-0015: NPC model variety sourcing
|
||||||
|
|
||||||
|
- **Status:** Accepted (planning)
|
||||||
|
- **Date:** 2026-11-03
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The library has eight skinned characters, and role/seed selection reuses them
|
||||||
|
across the three venues. Crowds therefore repeat the same faces, builds and
|
||||||
|
outfits. The existing contract is strict: `npc_base` mesh node, 13 named bones at
|
||||||
|
fixed rest positions, five tint slots, front on Blender −Y and an 8,000
|
||||||
|
triangle/vertex budget.
|
||||||
|
|
||||||
|
A review compared free/open-source sources against that contract. Quaternius
|
||||||
|
Ultimate Modular (CC0), Kenney Blocky Characters (CC0), Khronos CesiumMan and
|
||||||
|
RiggedFigure (CC BY 4.0) and the MakeHuman base mesh (CC0) were imported and
|
||||||
|
measured. None was a drop-in; the full results are in
|
||||||
|
[`docs/npc-model-sourcing.md`](../npc-model-sourcing.md).
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
1. **Ship only CC0, or CC BY with recorded attribution.** No service asset whose
|
||||||
|
terms restrict redistribution may be used.
|
||||||
|
2. **Extend the project-owned procedural generator as the primary path** for
|
||||||
|
shipped NPC variety. The first target is four crowd-variety looks, bringing
|
||||||
|
the library to twelve characters.
|
||||||
|
3. **Keep Quaternius as a reviewed reference/prototype, not a shipped base.** A
|
||||||
|
contract retarget is a no-go because the source is a T-pose 62-bone realistic
|
||||||
|
rig with substantially different proportions from the arm-down 13-bone
|
||||||
|
contract. A future external-rig path would need its own ADR covering its
|
||||||
|
skeleton, animation source and per-model gear offsets.
|
||||||
|
4. **MakeHuman is deferred to a separate local development pipeline.** Its CC0
|
||||||
|
base mesh can be decimated into budget, but it needs retopology, a custom rig
|
||||||
|
and project-owned clothing before it is usable. It must not become a CI or
|
||||||
|
build dependency.
|
||||||
|
5. **Commit research previews with attribution.** CC-BY preview images are
|
||||||
|
development-only and are not bundled with the game or its release packages.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
### Positive
|
||||||
|
|
||||||
|
- The shipped library keeps one art direction, one skeleton and one audit gate.
|
||||||
|
- Crowd variety lands without a new runtime dependency or a second animation
|
||||||
|
system.
|
||||||
|
- The licence position is simple: project-owned assets plus explicit CC-BY
|
||||||
|
attribution where a preview or future asset requires it.
|
||||||
|
- The Quaternius evaluation removes a tempting but high-risk retarget from the
|
||||||
|
critical path.
|
||||||
|
|
||||||
|
### Negative
|
||||||
|
|
||||||
|
- Procedural variation is still limited by the existing face and clothing
|
||||||
|
vocabulary; four variants do not provide full age/body diversity.
|
||||||
|
- Quaternius' animation set remains unused unless a second rig path is approved.
|
||||||
|
- MakeHuman body-shape diversity is postponed.
|
||||||
|
- External previews add a small documentation footprint that must stay out of
|
||||||
|
release packages.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- **Retarget Quaternius onto the `npc_base` contract.** Rejected for now:
|
||||||
|
welding brings the geometry inside budget, but the rest pose and body
|
||||||
|
proportions differ enough that a contract rebuild would either distort the
|
||||||
|
model or require a full proportion warp.
|
||||||
|
- **Use Quaternius' own rig and animations directly.** Deferred: attractive
|
||||||
|
animation quality, but it adds a second skeleton/animation path and per-model
|
||||||
|
gear offsets to `NpcAvatar`.
|
||||||
|
- **Use MakeHuman as the primary body source.** Rejected for this milestone: high
|
||||||
|
polygon count, no rig in the base mesh and a separate authoring dependency.
|
||||||
|
- **Use Mixamo or Ready Player Me.** Rejected: service terms are not a simple
|
||||||
|
free/open-source redistribution basis for shipped meshes.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Architecture Decision Records
|
||||||
|
|
||||||
|
Each ADR records the context, decision, consequences and alternatives for a durable architecture choice.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- [0001-venue-driven-simulation-architecture](0001-venue-driven-simulation-architecture.md)
|
||||||
|
- [0002-de-escalation-first-escalation-model](0002-de-escalation-first-escalation-model.md)
|
||||||
|
- [0003-explicit-access-verification-system](0003-explicit-access-verification-system.md)
|
||||||
|
- [0004-local-single-player-first](0004-local-single-player-first.md)
|
||||||
|
- [0005-event-based-incident-recording](0005-event-based-incident-recording.md)
|
||||||
|
- [0006-godot-4-gdscript](0006-godot-4-gdscript.md)
|
||||||
|
- [0007-locomotion-and-input-layers](0007-locomotion-and-input-layers.md)
|
||||||
|
- [0008-cone-focus-and-head-ring](0008-cone-focus-and-head-ring.md)
|
||||||
|
- [0009-radial-action-menu](0009-radial-action-menu.md)
|
||||||
|
- [0010-procedural-npc-presentation](0010-procedural-npc-presentation.md)
|
||||||
|
- [0011-aim-free-selection-and-precision-walk](0011-aim-free-selection-and-precision-walk.md)
|
||||||
|
- [0012-surface-materials-and-generated-props](0012-surface-materials-and-generated-props.md)
|
||||||
|
- [0013-blender-authored-npc-characters](0013-blender-authored-npc-characters.md)
|
||||||
|
- [0014-runtime-image-root](0014-runtime-image-root.md)
|
||||||
|
- [0015-npc-model-variety-sourcing](0015-npc-model-variety-sourcing.md)
|
||||||
@@ -25,7 +25,7 @@ This document follows the structure of the arc42 architecture documentation temp
|
|||||||
## 2. Constraints
|
## 2. Constraints
|
||||||
|
|
||||||
- The first implementation should be small enough for a playable vertical slice.
|
- The first implementation should be small enough for a playable vertical slice.
|
||||||
- The engine is Godot 4 with GDScript; a playable prototype vertical slice is implemented.
|
- The engine is Godot 4.7.2 with GDScript; a playable prototype vertical slice is implemented. CI, local development and exported builds use 4.7.2; the default renderer stays GL Compatibility for Web parity, with an optional Forward+ Desktop HQ build.
|
||||||
- Venue-specific policies must be configurable rather than hard-coded into individual NPCs.
|
- Venue-specific policies must be configurable rather than hard-coded into individual NPCs.
|
||||||
- The game must preserve the de-escalation-first design described in [`PLAN.md`](../PLAN.md).
|
- The game must preserve the de-escalation-first design described in [`PLAN.md`](../PLAN.md).
|
||||||
- The project uses the repository workflow described in [`AGENTS.md`](../AGENTS.md).
|
- The project uses the repository workflow described in [`AGENTS.md`](../AGENTS.md).
|
||||||
@@ -37,7 +37,7 @@ This document follows the structure of the arc42 architecture documentation temp
|
|||||||
- **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible.
|
- **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible.
|
||||||
- **PATCH** — bug fixes and small polish that do not change behavior.
|
- **PATCH** — bug fixes and small polish that do not change behavior.
|
||||||
|
|
||||||
The single source of truth for the version is `config/version` in `project.godot` (currently `0.7.2`). Each release is tagged in git as `v<version>` (e.g. `v0.6.0`). The `0.x` series is pre-1.0, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md).
|
The single source of truth for the version is `config/version` in `project.godot` (currently `0.10.0-rc.1`). Each release is tagged in git as `v<version>` (e.g. `v0.6.0`). The `0.x` series is pre-stable, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md).
|
||||||
|
|
||||||
## 3. Context and Scope
|
## 3. Context and Scope
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ The player interacts with NPCs, venue entrances, access artifacts, equipment, st
|
|||||||
- `tools/model_pipeline.py` builds and audits the street-prop `.glb` models and packs their colour atlases.
|
- `tools/model_pipeline.py` builds and audits the street-prop `.glb` models and packs their colour atlases.
|
||||||
- `tools/npc_pipeline.py` builds and audits the skinned NPC base model (`assets/models/generated/npc_base.glb`) and its per-slot material maps, with an optional AI fabric override (ADR-0013).
|
- `tools/npc_pipeline.py` builds and audits the skinned NPC base model (`assets/models/generated/npc_base.glb`) and its per-slot material maps, with an optional AI fabric override (ADR-0013).
|
||||||
- `tools/blender_assets.py` builds, renders and audits the Blender asset library (`assets/models/blender/{npc,npc_gear,props}/` + `manifest.json`): NPC character variants that are drop-in for `npc_base`, attach-point gear, and street/venue props. `NpcAvatar` selects variants/gear by role/seed and `WorldBuilder` loads the prop replacements and new street dressing, with procedural fallbacks (see `docs/blender-assets.md`).
|
- `tools/blender_assets.py` builds, renders and audits the Blender asset library (`assets/models/blender/{npc,npc_gear,props}/` + `manifest.json`): NPC character variants that are drop-in for `npc_base`, attach-point gear, and street/venue props. `NpcAvatar` selects variants/gear by role/seed and `WorldBuilder` loads the prop replacements and new street dressing, with procedural fallbacks (see `docs/blender-assets.md`).
|
||||||
- `tools/scene_concepts.py` generates realism-pass references (`docs/screens/concepts/`), skyline backdrops and surface tiles (raw `assets/textures/ai/v2/raw/` → candidates `assets/textures/ai/game_ready/v2/`), promotes the validated ones to the tracked `v2/picks/` that `SurfaceMaterials`/`WorldBuilder` preload, audits them, and scores `src/test/capture_realism.gd` A/B captures (five stages, runtime texture swaps) against the concepts (`docs/screens/realism/<run>/`).
|
- `tools/scene_concepts.py` generates realism-pass references (`docs/screens/concepts/`), skyline backdrops and surface tiles (raw `assets/textures/ai/v2/raw/` → candidates `assets/textures/ai/game_ready/v2/`), promotes the validated ones to the tracked `v2/picks/` that `SurfaceMaterials`/`WorldBuilder` preload, audits them, and scores `src/test/capture_realism.gd` A/B captures (six stages, runtime texture swaps) against the concepts (`docs/screens/realism/<run>/`).
|
||||||
- `tools/asset_pipeline.py` generates and audits role-aware textures; `tools/screen_designs.py` generates screen references and judges captures; `tools/check_screenshots.sh` renders the staged gallery and runs the compare gate.
|
- `tools/asset_pipeline.py` generates and audits role-aware textures; `tools/screen_designs.py` generates screen references and judges captures; `tools/check_screenshots.sh` renders the staged gallery and runs the compare gate.
|
||||||
|
|
||||||
Confirmed game assets live in `assets/textures/ai/game_ready/` (audited surface, UI, and card textures), `assets/models/generated/` (prop and NPC `.glb` models plus their texture atlases), and `assets/textures/npc/` (NPC material maps and `palette.json`). Development-only outputs — AI concept sheets, Blender sources, raw AI fabrics — are gitignored and regenerated by the tools.
|
Confirmed game assets live in `assets/textures/ai/game_ready/` (audited surface, UI, and card textures), `assets/models/generated/` (prop and NPC `.glb` models plus their texture atlases), and `assets/textures/npc/` (NPC material maps and `palette.json`). Development-only outputs — AI concept sheets, Blender sources, raw AI fabrics — are gitignored and regenerated by the tools.
|
||||||
@@ -106,6 +106,111 @@ When reference art becomes runtime art, copy or process it into `assets/images/`
|
|||||||
new descriptive name (never overwrite an existing image) and keep a procedural fallback
|
new descriptive name (never overwrite an existing image) and keep a procedural fallback
|
||||||
in code.
|
in code.
|
||||||
|
|
||||||
|
### Content and asset pipeline architecture
|
||||||
|
|
||||||
|
The asset/content pipeline is part of the architecture, not an external build detail:
|
||||||
|
|
||||||
|
| Stage | Producer | Committed artifact | Runtime consumer | Failure fallback |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| Scenario data | `ScenarioFactory` / editor export | `content/venues/*.tres`, event/profile data | `VenueScenario` → `ShiftCoordinator` | built-in scenario defaults |
|
||||||
|
| NPC library | Blender flatpak + `tools/blender_assets.py` | `assets/models/blender/npc/*.glb`, manifest, baked AO | `NpcAvatar` | `npc_base.glb` → procedural rig |
|
||||||
|
| Gear/props | Blender flatpak + tool + bake | `npc_gear/*.glb`, `props/*.glb`, `*_ao.png` | `NpcAvatar`, `WorldBuilder` | primitive assets / generated props |
|
||||||
|
| Surface/UI art | Python pipelines / AI-assisted references | `assets/textures/ai/game_ready/`, `assets/images/` | `SurfaceMaterials`, `UiArt` | neutral material / procedural UI |
|
||||||
|
| Screenshots/QA | capture scripts + `scene_concepts`/`screen_designs` | `docs/screenshots/`, `docs/screens/realism/` | reviewers and CI gates | regenerate; never runtime input |
|
||||||
|
| Local map data (planned) | OSM/3D building extract + licensed reference photos + AI/splat evaluation | derived map assets with provenance sidecars | `WorldBuilder` venue specs | current stylized venue geometry |
|
||||||
|
| Release | Gitea workflow + build/publish scripts | platform binaries, Web zip, Desktop HQ zip | players | source build |
|
||||||
|
|
||||||
|
Contracts that keep the pipeline replaceable:
|
||||||
|
|
||||||
|
- `npc_base` armature/bone names, five tint slots and front direction are frozen by tests; gear is authored in attach-point space and measured against the character silhouette.
|
||||||
|
- Library characters, gear and props are audited for grounding, UVs, triangle budgets, degenerate triangles and manifest coverage; Blender is a developer tool, never a CI runtime dependency.
|
||||||
|
- `WorldBuilder.VENUE_SPECS` drives geometry, navigation, features and prop placements; `GameManager` rebuilds only the world container on a venue change.
|
||||||
|
- UI/scene art is loaded through `UiArt` with a null/neutral fallback; composed images live in `assets/images/`, not in the docs reference tree (ADR-0014).
|
||||||
|
- CI stays offline: only committed `.glb`/`.png`/`.tres` and Godot are needed; optional AI/Blender steps are reproducible development tools.
|
||||||
|
|
||||||
|
### Character development tooling
|
||||||
|
|
||||||
|
Characters have two authored sources and one procedural fallback. The eight-variant
|
||||||
|
Blender library (`assets/models/blender/npc/`) is the normal presentation path; the
|
||||||
|
older skinned `npc_base` (`assets/models/generated/npc_base.glb`) remains a frozen
|
||||||
|
contract and fallback; `NpcAvatar`'s procedural rig is the offline last resort.
|
||||||
|
Four crowd-variety prototypes have been built and audited locally but are not yet in
|
||||||
|
the shipped catalogue; generator-first expansion is the active character track
|
||||||
|
(ADR-0015). Blender is a developer tool only — runtime, tests and CI consume committed
|
||||||
|
GLBs and maps.
|
||||||
|
|
||||||
|
| Tool | Role | Committed artifact | Verification gate |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `tools/npc_pipeline.py` | Builds the skinned base avatar, five-slot material maps, palette and AI-fabric preparation (`prepare`/`palette`) | `npc_base.glb`, `assets/textures/npc/*_{albedo,normal,orm}.png`, `palette.json` | `audit_glb` against the frozen 6,000-triangle / 4,000-vertex base contract |
|
||||||
|
| `tools/blender_assets.py` | Builds/renders the library: 8 characters, 18 gear pieces, 20 props, plus `manifest.json`; stdlib `audit`/`fit`/`sheet`/`list` | `assets/models/blender/{npc,npc_gear,props}/*.glb` | Library cap 8,000 tris / 8,000 verts per character, 1,500 per gear, 4,000 per prop; grounding, UV, degenerate-triangle, attach-fit and manifest coverage checks |
|
||||||
|
| `tools/bake_ao.py` | Bakes per-asset AO maps | `<asset>_ao.png` (256²) | Wired through `Materials.apply_ao()`/`SurfaceMaterials`; cloth/pants opt out (see below) |
|
||||||
|
| `tools/npc_proportions.py` | Measures exported GLB rest pose (height, head, shoulders, hips, torso, limbs, feet) against the stylized guide | report / JSON in `docs/npc-proportion-audit.md` | All eight variants must stay inside the guide after a body change |
|
||||||
|
| `tools/npc_baseline.py` | Assembles neutral-baseline sheets and before/after comparisons | `docs/blender_previews/sheet_npc_baseline*.jpg` | Human review against `docs/npc-visual-review.md` |
|
||||||
|
| `src/test/capture_npc_baseline.gd` | Fixed-camera studio baseline, 8 variants × 6 views | local `tmp/` frames | Neutral silhouette/face/gear review |
|
||||||
|
| `src/test/capture_npc_review.gd` | Final-context gate: 8 variants × 2 venues × day/night × 3 distances = 96 frames, crowd hidden, review-only `character_override` for the guard | local `tmp/` frames | Pre-build acceptance gate for gear fit and venue lighting |
|
||||||
|
| `src/test/capture_realism.gd` | Venue realism stages plus a frozen portrait and surface close-ups | `docs/screens/realism/<run>/` | Optional vision judge; portrait is the only avatar-detail capture |
|
||||||
|
|
||||||
|
Runtime contracts that must survive any character change:
|
||||||
|
|
||||||
|
- `NpcAvatar.character_model_for(role, seed)` selects the library variant; `character_override`
|
||||||
|
exists only to stage roles without a spawned model (for example the guard).
|
||||||
|
- The armature node names, `Rig/Head`, the Hip/Knee/Shoulder/Elbow/Hand proxies and the
|
||||||
|
**+Z visual front** are frozen; animation and `yaw_towards()` depend on them.
|
||||||
|
- Only the five contract slots are tinted. Fixed detail materials (eyes, reflective bands,
|
||||||
|
rubber) stay untouched, gear is placed by `GEAR_ATTACH`, and `GEAR_OFFSETS` plus
|
||||||
|
`HAND_ITEM_FORWARD` are runtime attach-space corrections, not authoring changes.
|
||||||
|
- Cloth/pants use `Materials.NPC_FABRIC_UV_SCALE` (4×) and skip the baked per-character AO;
|
||||||
|
skin/hair/gear keep AO. Do not reintroduce the blotchy cloth AO.
|
||||||
|
- `apply_lod()` swaps to an impostor at 26 m; the 24-NPC budget smoke (6.94 ms, 168 meshes,
|
||||||
|
131,616 triangles) is the performance gate. Changing either triangle cap requires
|
||||||
|
re-measuring that smoke and the Web/Compatibility path.
|
||||||
|
- Rebuild only the assets whose geometry changed (Blender GLB export is byte-nondeterministic),
|
||||||
|
then run `godot --headless --path . --import`; never commit a no-op full rebuild.
|
||||||
|
|
||||||
|
Evidence and detailed contracts live in [`blender-assets.md`](blender-assets.md),
|
||||||
|
[`npc-visual-review.md`](npc-visual-review.md) and
|
||||||
|
[`npc-proportion-audit.md`](npc-proportion-audit.md); command-level usage is in
|
||||||
|
[`asset-tooling.md`](asset-tooling.md).
|
||||||
|
|
||||||
|
### Local map development tooling (planned)
|
||||||
|
|
||||||
|
The current venues are hand-authored procedural reconstructions in
|
||||||
|
`src/game/world_builder.gd`. The **Local map development** track in
|
||||||
|
[`TODO.md`](../TODO.md#local-map-development) is unversioned and planned: it will
|
||||||
|
test whether licensed data and reference photography can replace or supplement the
|
||||||
|
stylized geometry without moving map data, AI composition or splat rendering into
|
||||||
|
the runtime.
|
||||||
|
|
||||||
|
Planned stages and gates:
|
||||||
|
|
||||||
|
| Stage | Source | Output | Gate |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Footprints and massing | Offline OpenStreetMap extract + licence-compatible 3D building data (OSM Buildings, Overture, CityGML/LOD1–2) | Local footprint/massing scenes, extract date and query pinned | ODbL attribution/share-alike review; scale/orientation and venue-footprint comparison |
|
||||||
|
| Reference imagery | Wikimedia Commons, own/team photos or other clearly licensed images | Reference boards and material candidates | Written author/licence/attribution, privacy redaction, no unlicensed Street View or scraped commercial imagery |
|
||||||
|
| AI-assisted composition | Project image tooling | Palette/facade-rhythm boards and material candidates | Model/prompt/output recorded; human review; never treated as licensed map data |
|
||||||
|
| Photoreal facade kit | Project-authored geometry and derived materials | Committed facade modules and PBR maps | WorldBuilder budget, collider/nav coverage, Compatibility-readability and screenshot review |
|
||||||
|
| Gaussian splatting | Licensed/permissioned photo sets + pinned offline toolchain | Development-only splat, texture or proxy experiments | Per-path ADR; runtime only if performance, collision/navigation and provenance are proven |
|
||||||
|
|
||||||
|
Contracts for any future map source:
|
||||||
|
|
||||||
|
- Runtime consumes committed, derived assets only; no online OSM, photo or splat
|
||||||
|
service is a build or play dependency.
|
||||||
|
- OSM ODbL, photo licences, capture permissions and privacy redaction are recorded
|
||||||
|
in a provenance sidecar before an asset enters the repository.
|
||||||
|
- Collision and navigation stay mesh-based; a splat may never become the playable
|
||||||
|
floor or the access/occlusion authority.
|
||||||
|
- The stylized venue geometry remains the fallback until a data-backed block passes
|
||||||
|
the venue budget, navigation, visual and provenance gates.
|
||||||
|
|
||||||
|
### Content authoring and runtime data flow
|
||||||
|
|
||||||
|
`ScenarioFactory` is the source of truth for the three authored venues. `tools/export_venues.gd`
|
||||||
|
writes `content/venues/<id>.tres` for editor inspection; the factory remains the canonical
|
||||||
|
scenario definition. At runtime `GameManager` selects a `VenueScenario`, `ShiftCoordinator`
|
||||||
|
schedules events and phases, and `NPCController`/`AccessVerifier`/`EscalationManager` produce
|
||||||
|
incidents. `IncidentRecorder` is the authoritative event history; body-camera review,
|
||||||
|
reputation, crowd reaction and shift review read it rather than resolving gameplay separately.
|
||||||
|
|
||||||
## 6. Runtime View
|
## 6. Runtime View
|
||||||
|
|
||||||
### Routine interaction
|
### Routine interaction
|
||||||
@@ -118,6 +223,20 @@ in code.
|
|||||||
6. The incident recorder stores the interaction.
|
6. The incident recorder stores the interaction.
|
||||||
7. Reputation and crowd systems react to the outcome.
|
7. Reputation and crowd systems react to the outcome.
|
||||||
|
|
||||||
|
### Venue switch
|
||||||
|
|
||||||
|
1. The player selects a venue from the start menu or cycles with the debug shortcut.
|
||||||
|
2. `GameManager` loads the matching `VenueScenario` and keeps the guard/UI state.
|
||||||
|
3. `WorldBuilder` rebuilds only `world_root` from `VENUE_SPECS`, including geometry, props and the navigation mesh.
|
||||||
|
4. Shift state, NPCs and incidents are reset for the new venue; phase lighting and the door state are reapplied.
|
||||||
|
5. The menu's **Hollywood Night** row resolves to `hollywood_building`, then sets `ShiftCoordinator.phase_offset_s` so elapsed time starts at 0 but phase lookup starts at `LATE`. The offset is part of the checkpoint snapshot, so a resumed night shift stays at night.
|
||||||
|
|
||||||
|
### Asset fallback
|
||||||
|
|
||||||
|
1. A runtime loader asks for the library scene or texture.
|
||||||
|
2. If the imported asset is missing, the loader uses the generated/procedural fallback rather than failing the boot.
|
||||||
|
3. Contracts and audits ensure the fallback and library paths expose the same observable behaviour; CI runs headless against whichever path is available.
|
||||||
|
|
||||||
### Access check
|
### Access check
|
||||||
|
|
||||||
1. The player stops or receives an approaching person.
|
1. The player stops or receives an approaching person.
|
||||||
@@ -137,7 +256,7 @@ in code.
|
|||||||
|
|
||||||
## 7. Deployment View
|
## 7. Deployment View
|
||||||
|
|
||||||
The game is a single-player local client containing presentation, simulation, rules, content, and persistence for local settings and shift results. No online backend is required. A non-threaded Web export ships alongside the desktop builds and can be served from any static host without special headers.
|
The game is a single-player local client containing presentation, simulation, rules, content, and persistence for local settings and shift results. No online backend is required. A non-threaded Web export ships alongside the desktop builds and can be served from any static host without special headers. The default renderer is GL Compatibility; an optional Desktop HQ zip uses Forward+ with volumetric fog/SDFGI on Vulkan-capable desktops. CI, local development and exported builds use Godot 4.7.2; the light-falloff code keeps a compatibility branch for older editors but the supported baseline is unified.
|
||||||
|
|
||||||
Future options include external analytics, cloud saves, or replay sharing, but they are not part of the current architecture.
|
Future options include external analytics, cloud saves, or replay sharing, but they are not part of the current architecture.
|
||||||
|
|
||||||
@@ -145,7 +264,7 @@ Future options include external analytics, cloud saves, or replay sharing, but t
|
|||||||
|
|
||||||
A continuous-integration pipeline keeps the headless test suite green on every change and publishes tagged releases.
|
A continuous-integration pipeline keeps the headless test suite green on every change and publishes tagged releases.
|
||||||
|
|
||||||
- **Workflow:** `.gitea/workflows/build.yml` runs on pushes, pull requests, tags, and manual dispatches. The `checks` job installs Godot 4.3, imports the project, and runs `res://src/test/test_runner.gd`; it fails on a non-zero exit code or any `SCRIPT ERROR` in the output.
|
- **Workflow:** `.gitea/workflows/build.yml` runs on pushes, pull requests, tags, and manual dispatches. The `checks` job installs Godot 4.7.2, imports the project, and runs `res://src/test/test_runner.gd`; it fails on a non-zero exit code or any `SCRIPT ERROR` in the output.
|
||||||
- **Build job:** runs for tags and manual dispatches. It installs export templates, exports Linux/Windows/macOS and the non-threaded Web build, packages the NSIS installer, Linux AppImage and optional Desktop HQ zip, verifies the installer contents, and uploads the artifacts.
|
- **Build job:** runs for tags and manual dispatches. It installs export templates, exports Linux/Windows/macOS and the non-threaded Web build, packages the NSIS installer, Linux AppImage and optional Desktop HQ zip, verifies the installer contents, and uploads the artifacts.
|
||||||
- **Publish job:** tag builds call `tools/publish_release.sh` to create the Gitea release and upload the standard artifacts plus the optional Desktop HQ zip. The script uses `GITEA_TOKEN` when configured and otherwise reuses the runner's checkout extraheader credentials.
|
- **Publish job:** tag builds call `tools/publish_release.sh` to create the Gitea release and upload the standard artifacts plus the optional Desktop HQ zip. The script uses `GITEA_TOKEN` when configured and otherwise reuses the runner's checkout extraheader credentials.
|
||||||
- **Hosting:** the repository lives on Gitea (`https://git.allucanget.biz/zwitschi/security-game`). Export templates must match the pinned Godot version; macOS builds are unsigned and need quarantine guidance or notarization for wider distribution.
|
- **Hosting:** the repository lives on Gitea (`https://git.allucanget.biz/zwitschi/security-game`). Export templates must match the pinned Godot version; macOS builds are unsigned and need quarantine guidance or notarization for wider distribution.
|
||||||
@@ -173,6 +292,11 @@ The release process is: bump the version, run the full test suite and `tools/che
|
|||||||
- **Localization:** Dialogue and prompts should be externalized from logic.
|
- **Localization:** Dialogue and prompts should be externalized from logic.
|
||||||
- **Observability:** Debug overlays show NPC intent, access status, escalation level, active incidents, and response timers during development.
|
- **Observability:** Debug overlays show NPC intent, access status, escalation level, active incidents, and response timers during development.
|
||||||
- **Visual regression:** Staged captures are compared against the runtime reference art (`screen_designs.py compare`) and optionally judged by a vision model; `tools/check_screenshots.sh` makes the pass repeatable.
|
- **Visual regression:** Staged captures are compared against the runtime reference art (`screen_designs.py compare`) and optionally judged by a vision model; `tools/check_screenshots.sh` makes the pass repeatable.
|
||||||
|
- **Asset provenance and AI usage:** AI-assisted images/concepts are development inputs only; validated outputs are committed, audited and loaded through the same runtime asset contracts. Generated content must not become an offline/CI dependency.
|
||||||
|
- **Campaign and checkpoint state:** `Campaign` owns venue order/progression; settings, campaign, completed shift history and a versioned pause checkpoint are separate JSON documents. The checkpoint snapshots coordinator systems, scheduler RNG and NPC presentation/access state; it deliberately does not serialize the tutorial overlay or transient input state.
|
||||||
|
- **Difficulty:** easy/normal/hard only scale spawn pacing, NPC compliance tolerance and report-credibility strictness; access/escalation rules and emergency rarity are invariant.
|
||||||
|
- **Settings and accessibility:** volume/motion/caption settings persist through `settings.json`; Help is generated from the input-binding catalogue and the HUD log is the text caption surface. CI/release, local development and exported builds are unified on Godot 4.7.2.
|
||||||
|
- **Documentation ownership:** `PLAN.md` owns design intent; `docs/gameplay.md` owns playable mechanics; `docs/architecture.md` owns system boundaries, data flow, constraints and cross-cutting decisions; ADRs own decision history; `docs/development-status.md` owns the implemented/gap list; `TODO.md` owns the roadmap; `MEMORY.md` owns gotchas and open questions. Specialized docs link to these rather than becoming competing sources of truth.
|
||||||
|
|
||||||
## 9. Architecture Decisions
|
## 9. Architecture Decisions
|
||||||
|
|
||||||
@@ -203,6 +327,9 @@ Architecture decisions are recorded as ADRs:
|
|||||||
| Performance | The target venue should support a believable crowd without harming input or simulation responsiveness. |
|
| Performance | The target venue should support a believable crowd without harming input or simulation responsiveness. |
|
||||||
| Testability | Access and escalation rules can be tested independently of presentation. |
|
| Testability | Access and escalation rules can be tested independently of presentation. |
|
||||||
| Accessibility | Core actions are available through clear input alternatives and readable feedback. |
|
| Accessibility | Core actions are available through clear input alternatives and readable feedback. |
|
||||||
|
| Reliability | A fresh checkout, missing optional asset, invalid AI key, or unavailable Forward+ renderer must not prevent a normal Compatibility/Web/headless boot. |
|
||||||
|
| Performance | A busy venue should hold its budget at the target NPC cap on desktop and Web; the current 24-NPC check is a baseline, not a release guarantee. |
|
||||||
|
| Documentation integrity | Every released version has a changelog entry, current status/roadmap, and architecture/ADR references that match the build. |
|
||||||
|
|
||||||
## 11. Risks and Technical Debt
|
## 11. Risks and Technical Debt
|
||||||
|
|
||||||
@@ -213,8 +340,49 @@ Architecture decisions are recorded as ADRs:
|
|||||||
- Mental-health-crisis scenarios require careful writing, playtesting, and respectful presentation.
|
- Mental-health-crisis scenarios require careful writing, playtesting, and respectful presentation.
|
||||||
- The choice of engine may affect navigation, crowd scale, tooling, and content authoring.
|
- The choice of engine may affect navigation, crowd scale, tooling, and content authoring.
|
||||||
- Reference mockups can outpace the runtime art style; compare/judge catch gross drift but not composition errors, so the staged gallery needs human review after large visual changes.
|
- Reference mockups can outpace the runtime art style; compare/judge catch gross drift but not composition errors, so the staged gallery needs human review after large visual changes.
|
||||||
|
- Godot 4.7.2 is the unified CI/local/project baseline; future engine bumps still require import, visual and export smoke checks before changing that baseline.
|
||||||
|
- Content volume and authoring tools are still thin; adding venues/events by editing the factory can become error-prone without schema validation and preview tooling.
|
||||||
|
- Save data currently covers settings and shift history, not mid-shift resume; adding saves/replays requires a versioned schema and migration policy.
|
||||||
|
- The optional judge and image-generation paths depend on an external key, model availability and budget; CI cannot make them a release gate, and keys must never be committed.
|
||||||
|
- Accessibility and localization coverage is partial; a public release needs subtitles/captions, colour-independent cues, remapping, text expansion tests and at least a documented localization workflow.
|
||||||
|
- Unsigned macOS builds, AI asset provenance and any storefront/legal requirements are release blockers for a public release.
|
||||||
|
- Documentation drift has already occurred (release version, implemented status and obsolete plan docs); the ownership map and roadmap must be kept current in the same change as the implementation.
|
||||||
|
- Character quality is only partly automatable: proportions, budgets and gear fit are measured, but face realism and venue-lighting readability still need the baseline/review sheets and human sign-off after any mesh, map or material change.
|
||||||
|
- Local-map data and photo-derived assets add a second provenance and privacy risk: OSM share-alike, photo rights, faces/plates, property permission and rapidly churning splat tooling must be resolved before any derived asset ships.
|
||||||
|
|
||||||
## 12. Glossary
|
## 12. Documentation Consolidation and Ownership
|
||||||
|
|
||||||
|
The documentation set is intentionally layered. Architecture-level material is consolidated here;
|
||||||
|
specialized operational detail remains in focused pages so commands do not rot inside the
|
||||||
|
architecture document.
|
||||||
|
|
||||||
|
| Document | Canonical role | Relationship to this architecture |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [`PLAN.md`](../PLAN.md) | Product/design intent, pillars, venues and success criteria | Design source; architecture realizes it |
|
||||||
|
| [`docs/gameplay.md`](gameplay.md) | Playable loop, escalation, venue mechanics | Runtime/behavioral view |
|
||||||
|
| [`docs/architecture.md`](architecture.md) | System boundaries, data flow, constraints, deployment, cross-cutting decisions, quality risks | Single architecture source |
|
||||||
|
| [`docs/adr/`](adr/) | Immutable decision records with context and consequences | Decision history linked from §9 |
|
||||||
|
| [`docs/asset-tooling.md`](asset-tooling.md) | Build/audit/capture commands and pipeline usage | Operational appendix to the pipeline section |
|
||||||
|
| [`docs/blender-assets.md`](blender-assets.md) | Blender character/gear/prop contracts and attach points | Detailed asset contract appendix |
|
||||||
|
| [`docs/npc-visual-review.md`](npc-visual-review.md) | NPC baseline views, acceptance criteria and ranked visual gaps | Visual-QA appendix to the character tooling section |
|
||||||
|
| [`docs/npc-proportion-audit.md`](npc-proportion-audit.md) | Measured proportions against the stylized guide | Measurement appendix to the character tooling section |
|
||||||
|
| [`docs/npc-model-sourcing.md`](npc-model-sourcing.md) | Character-source evaluation, licence rules and the character-development plan | Supports the character tooling section and ADR-0015 |
|
||||||
|
| [`docs/getting-started.md`](getting-started.md) | Setup, commands, controls | Developer onboarding |
|
||||||
|
| [`docs/RELEASING.md`](RELEASING.md) | Release checklist and CI/publishing steps | Deployment appendix to §7 |
|
||||||
|
| [`docs/smoke-checklist.md`](smoke-checklist.md) | Manual verification script | QA appendix to §10 |
|
||||||
|
| [`docs/development-status.md`](development-status.md) | Implemented features and current gaps | Status companion to the roadmap |
|
||||||
|
| [`TODO.md`](../TODO.md) | Priority-ordered active tasks, unversioned development tracks and versioned roadmap | Planning source; no architecture duplication |
|
||||||
|
| [`CHANGELOG.md`](../CHANGELOG.md) | User-facing release history | Release/version companion |
|
||||||
|
|
||||||
|
Consolidation decisions:
|
||||||
|
|
||||||
|
- **Architecture owns the "how"** (components, contracts, data flow, constraints, quality gates); **PLAN/gameplay own the "what"**; **ADRs own the "why"**. Links replace duplicated prose.
|
||||||
|
- **Asset and release documents remain separate** because they are command-heavy operational procedures; their architecture-relevant contracts and failure modes are summarized in §5 and §7.
|
||||||
|
- **Completed plans are removed, not kept as competing roadmaps.** Historical graphics work is summarized by ADR-0013, the asset pipeline section and the v0.6.0 changelog; new work belongs in TODO.
|
||||||
|
- **Status and roadmap are separate from architecture.** `docs/development-status.md` records what exists; `TODO.md` records what remains, ordered as unversioned development tracks, versioned milestones and deferred work.
|
||||||
|
- **Documentation drift is a tracked risk:** release/version, test counts, renderer status, and stale plans must be corrected in the same commit as the change that invalidates them.
|
||||||
|
|
||||||
|
## 13. Glossary
|
||||||
|
|
||||||
- **Access artifact:** A wristband, ID, ticket, credential, or other item used to verify entry.
|
- **Access artifact:** A wristband, ID, ticket, credential, or other item used to verify entry.
|
||||||
- **Authorized person:** Someone permitted to enter under the current venue policy.
|
- **Authorized person:** Someone permitted to enter under the current venue policy.
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
# Asset Pipeline and Tooling
|
# Asset Pipeline and Tooling
|
||||||
|
|
||||||
Everything the game renders is produced or gated by the scripts below. Development-only outputs (AI concept sheets, Blender sources, raw AI fabrics) are gitignored; the audited game assets are committed.
|
Everything the game renders is produced or gated by the scripts below. Architecture-level contracts,
|
||||||
|
data flow and failure fallbacks are summarized in
|
||||||
|
[`architecture.md`](architecture.md#content-and-asset-pipeline-architecture); this page is the
|
||||||
|
operational reference. Development-only outputs (AI concept sheets, Blender sources, raw AI fabrics)
|
||||||
|
are gitignored; the audited game assets are committed.
|
||||||
|
|
||||||
## Image Asset Tools
|
## Image Asset Tools
|
||||||
|
|
||||||
@@ -82,7 +86,8 @@ character authored in Blender ([ADR-0013](adr/0013-blender-authored-npc-characte
|
|||||||
per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its
|
per-slot material maps. `NpcAvatar` instantiates that skinned model, tints its
|
||||||
five material slots from the seed-driven outfit, and drives the bones from the
|
five material slots from the seed-driven outfit, and drives the bones from the
|
||||||
existing animation code; the procedural rig remains the offline fallback (see
|
existing animation code; the procedural rig remains the offline fallback (see
|
||||||
the [graphics plan](plan-graphics-v0.6.md)).
|
[ADR-0013](adr/0013-blender-authored-npc-characters.md) and
|
||||||
|
[`blender-assets.md`](blender-assets.md)).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Print the frozen contract (slots, bone names, triangle budget)
|
# Print the frozen contract (slots, bone names, triangle budget)
|
||||||
@@ -99,8 +104,11 @@ python3 tools/npc_pipeline.py palette
|
|||||||
```
|
```
|
||||||
|
|
||||||
`prepare` resizes the AI fabric tiles (kept locally in `assets/textures/npc/ai/`,
|
`prepare` resizes the AI fabric tiles (kept locally in `assets/textures/npc/ai/`,
|
||||||
which is gitignored) onto the `cloth`/`pants` albedos; `build` bakes them into the
|
which is gitignored) onto the 256² `cloth`/`pants` albedos; `build` bakes them into
|
||||||
`.glb` and regenerates the procedural normal + packed ORM maps. `palette` writes
|
the `.glb` and regenerates the procedural normal + packed ORM maps at the same
|
||||||
|
256² size. Runtime fabric materials repeat the weave 4× (`NPC_FABRIC_UV_SCALE`)
|
||||||
|
and skip the per-character baked AO, which otherwise magnified into large
|
||||||
|
blotches; skin, hair and fixed gear keep AO. `palette` writes
|
||||||
`assets/textures/npc/palette.json` using `model_pipeline.dominant_colors`.
|
`assets/textures/npc/palette.json` using `model_pipeline.dominant_colors`.
|
||||||
|
|
||||||
## Blender asset library (loaded by the game)
|
## Blender asset library (loaded by the game)
|
||||||
@@ -121,6 +129,11 @@ with colliders/avoidance and drives their emissive parts from the day/night
|
|||||||
cycle. The single `npc_base.glb` and primitive props remain offline fallbacks.
|
cycle. The single `npc_base.glb` and primitive props remain offline fallbacks.
|
||||||
Contract and integration notes: [`blender-assets.md`](blender-assets.md).
|
Contract and integration notes: [`blender-assets.md`](blender-assets.md).
|
||||||
|
|
||||||
|
Sourcing and expansion research (no external model is shipped yet):
|
||||||
|
[`npc-model-sourcing.md`](npc-model-sourcing.md) compares Quaternius, Kenney, Khronos and
|
||||||
|
MakeHuman sources against the character contract and records the 2026-11-03 direction
|
||||||
|
(procedural generator first, Quaternius reference, MakeHuman deferred).
|
||||||
|
|
||||||
| Characters | Props |
|
| Characters | Props |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|  |  |
|
|  |  |
|
||||||
@@ -130,9 +143,90 @@ flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets
|
|||||||
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render
|
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render
|
||||||
python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg
|
python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg
|
||||||
python3 tools/blender_assets.py audit # no Blender needed
|
python3 tools/blender_assets.py audit # no Blender needed
|
||||||
|
python3 tools/blender_assets.py fit # gear-vs-body fit report
|
||||||
|
python3 tools/blender_assets.py texels # per-slot texel density (px/m) of the characters
|
||||||
godot --headless --path . --import
|
godot --headless --path . --import
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Intake inspection (`inspect`)
|
||||||
|
|
||||||
|
`audit` reads accessor-local bounds and ignores node transforms, so it is the right
|
||||||
|
release gate but a poor first look at a downloaded or newly authored GLB. `inspect`
|
||||||
|
imports the file into Blender, clears parents and applies object transforms, then
|
||||||
|
reports what the scene actually contains:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# one asset, a batch, or an external download; repeat --file as needed
|
||||||
|
flatpak run org.blender.Blender --background \
|
||||||
|
--python "$PWD/tools/blender_assets.py" -- inspect \
|
||||||
|
--file "$PWD/assets/models/blender/npc/npc_casual.glb" \
|
||||||
|
--file "$PWD/tmp/downloads/SomeCharacter.glb"
|
||||||
|
# machine-readable, for intake scripts (write to a file: Blender prints a banner first)
|
||||||
|
... -- inspect --file "$PWD/tmp/x.glb" --json --out "$PWD/tmp/x.json"
|
||||||
|
# force a contract family instead of inferring it from the path
|
||||||
|
... -- inspect --file "$PWD/tmp/x.glb" --category npc
|
||||||
|
```
|
||||||
|
|
||||||
|
It prints triangles (with the category budget), raw and seam-split vertex counts,
|
||||||
|
Godot-space bounds with height/feet for characters, whether transforms had to be
|
||||||
|
applied, mesh nodes and armatures, bone names, materials, UV layers, skinning and
|
||||||
|
animation count. Hard contract failures (`issues`) fail the command; advisory
|
||||||
|
findings (`warnings`) do not. The category is inferred from the path:
|
||||||
|
`blender/npc` → `npc`, `blender/npc_gear` → `npc_gear`, `blender/props` and
|
||||||
|
`generated` → `props`, anything else → `external` (measured but with no project
|
||||||
|
budget or rig contract).
|
||||||
|
|
||||||
|
Caveats:
|
||||||
|
|
||||||
|
- The glTF importer adds a bone-display mesh (`Icosphere`, used as
|
||||||
|
`pose_bone.custom_shape`). `inspect` excludes custom-shape helpers so they never
|
||||||
|
pollute bounds or the unskinned list.
|
||||||
|
- Bounds and bone rests are converted from Blender Z-up to Godot Y-up (`x, z, -y`),
|
||||||
|
matching the contract bands and the audit's coordinate space.
|
||||||
|
- Numbers are Blender-side measurements. Triangles match `audit` on project assets; the seam-split
|
||||||
|
vertex count can differ by ±1 because the importer rounds normals. `audit` stays the single final
|
||||||
|
gate: it is stdlib-only, runs in CI, and matches what the engine imports.
|
||||||
|
- `inspect` never writes or adapts the file; scale/grounding/weld/retarget work is the
|
||||||
|
separate (not yet built) `adapt` step.
|
||||||
|
- `--json` also captures the importer's INFO log in `stats.import_log` instead of printing it.
|
||||||
|
Blender still prints its own version banner before the script runs, so intake scripts should use
|
||||||
|
`--json --out <file>` rather than parsing stdout.
|
||||||
|
|
||||||
|
### Texel density (`texels`)
|
||||||
|
|
||||||
|
`texel_density()` and the `texels` command compute `resolution * sqrt(UV area / world area)` per
|
||||||
|
material slot straight from the GLB, so the number does not depend on island packing. The NPC slot
|
||||||
|
maps are 256², and cloth/pants are reported twice: authored density and effective density with the
|
||||||
|
runtime 4× repeat from `Materials.NPC_FABRIC_UV_SCALE`. Current values and interpretation are in
|
||||||
|
[`3d-model-review.md`](3d-model-review.md).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/blender_assets.py texels # all eight characters
|
||||||
|
python3 tools/blender_assets.py texels --json # machine-readable
|
||||||
|
python3 tools/blender_assets.py texels --only npc_coat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local map development (planned)
|
||||||
|
|
||||||
|
The **Local map development** track in [`TODO.md`](../TODO.md#local-map-development)
|
||||||
|
is not implemented yet; there are no map-generation commands to run. The planned
|
||||||
|
pipeline will evaluate:
|
||||||
|
|
||||||
|
- offline OpenStreetMap extracts for footprints and licence-compatible 3D building
|
||||||
|
massing (OSM Buildings, Overture, CityGML/LOD1–2);
|
||||||
|
- downloaded reference imagery from Wikimedia Commons, own/team photography or
|
||||||
|
other clearly licensed sources, with author/licence/attribution and privacy
|
||||||
|
redaction recorded per set;
|
||||||
|
- AI-assisted composition boards for palette, facade rhythm and material study,
|
||||||
|
with model/prompt/output provenance and human acceptance;
|
||||||
|
- Gaussian splatting as a development-only capture/reference/texture/proxy tool,
|
||||||
|
version-pinned offline and outside CI, until an ADR proves runtime safety.
|
||||||
|
|
||||||
|
No online map/photo/splat service is a build or play dependency. Committed map
|
||||||
|
assets will need provenance sidecars, ODbL/photo-licence review and the same
|
||||||
|
`WorldBuilder` venue-budget/navigation/collider gates as the current stylized
|
||||||
|
geometry. The detailed task plan and exit criteria live in `TODO.md`.
|
||||||
|
|
||||||
## Confirmed game assets
|
## Confirmed game assets
|
||||||
|
|
||||||
Everything below is audited and shipped in the game; development-only outputs
|
Everything below is audited and shipped in the game; development-only outputs
|
||||||
@@ -191,26 +285,95 @@ and ranks them by advertised token/image price. The run falls through the list
|
|||||||
on provider/model errors and aborts on an invalid key. Use
|
on provider/model errors and aborts on an invalid key. Use
|
||||||
`GIG_LINE_JUDGE_MODEL` or `--model <id>` for a pinned comparison.
|
`GIG_LINE_JUDGE_MODEL` or `--model <id>` for a pinned comparison.
|
||||||
|
|
||||||
`capture_realism.gd` runs six stages (day, portrait, evening, late, Joke Shop, studio);
|
`capture_npc_review.gd` is the pre-build NPC gate: it stages all eight
|
||||||
`--stages=day,late` limits it while tuning; `--flashlight` turns on the guard lamp for
|
library character models at portrait, conversation and crowd distance on both
|
||||||
night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one NPC on open sidewalk
|
promoted maps in day and night lighting, with the normal crowd hidden. It uses
|
||||||
and frames the face, which is the only capture that can judge avatar detail
|
the review-only `NpcAvatar.character_override` to include the guard model, which
|
||||||
(`--portrait-type=N` picks a role). The texture-adoption run,
|
has no spawned NPC role:
|
||||||
[`docs/screens/realism/2026-11-01-adopt/`](../docs/screens/realism/2026-11-01-adopt/report.md),
|
|
||||||
compares the pre-adoption captures with the current build: on the shots both sets captured the
|
|
||||||
judge's like-for-like means rose on every sub-score (overall 0.8 → 1.0, backdrop 0.6 → 1.6) and
|
|
||||||
the Pillow metric gap fell 0.444 → 0.348. The character pass
|
|
||||||
([`2026-11-01-npc/`](../docs/screens/realism/2026-11-01-npc/report.md)) adds the portrait shot and
|
|
||||||
records the new faces, hands and sleeve cuffs. The
|
|
||||||
Forward+ A/B ([`2026-11-01-forward-plus/`](../docs/screens/realism/2026-11-01-forward-plus/report.md))
|
|
||||||
compares the Compatibility entrance with the optional Desktop HQ preset.
|
|
||||||
The gear-fit run
|
|
||||||
([`2026-11-01-gear/`](../docs/screens/realism/2026-11-01-gear/report.md)) checks
|
|
||||||
the sunglasses, bag and helmet against measured head/skirt bounds with
|
|
||||||
`python3 tools/blender_assets.py fit`; it also fixed the portrait yaw and hid
|
|
||||||
the focus overlay so the close-up shows the avatar.
|
|
||||||
|
|
||||||

|
```bash
|
||||||
|
godot --path . --script res://src/test/capture_npc_review.gd -- \
|
||||||
|
--out=res://tmp/npc-review
|
||||||
|
```
|
||||||
|
|
||||||
|
Contact sheets can be assembled from the files (the output is local QA and is
|
||||||
|
not committed). The runtime applies small attach-space offsets to the lanyard
|
||||||
|
and firefighter helmet so the badge clears the jacket and the brim does not sit
|
||||||
|
over the eyes; these are `NpcAvatar.GEAR_OFFSETS`, not changes to the gear
|
||||||
|
contract.
|
||||||
|
|
||||||
|
`tools/npc_proportions.py` checks the eight character GLBs against the shared
|
||||||
|
height/head/shoulder/hip/torso/leg/arm/foot guide; the current results are in
|
||||||
|
[`npc-proportion-audit.md`](npc-proportion-audit.md). It reads the exported GLBs
|
||||||
|
and does not require Blender.
|
||||||
|
|
||||||
|
`capture_npc_baseline.gd` is the 0.9.4 neutral baseline: eight characters ×
|
||||||
|
front/three-quarter/profile/back/full-body/crowd = 48 frames with a fixed
|
||||||
|
camera, studio lighting and no venue/HUD/animation. The raw PNGs stay in
|
||||||
|
`tmp/`; `tools/npc_baseline.py sheet` builds the committed contact sheet, and
|
||||||
|
`tools/npc_baseline.py compare --before DIR --after DIR` builds the review
|
||||||
|
comparison after a model change. See
|
||||||
|
[`npc-visual-review.md`](npc-visual-review.md).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||||||
|
--out=res://tmp/npc-baseline
|
||||||
|
python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
`capture_realism.gd` runs nine stages in this order: day, Hollywood surface close-ups,
|
||||||
|
portrait, evening, late, Joke Shop day, Joke Shop surface close-ups, Joke Shop night,
|
||||||
|
studio; `--stages=day,late` limits it while tuning; `--flashlight` turns on the guard
|
||||||
|
lamp for night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one
|
||||||
|
NPC on open sidewalk and frames the face, which is the only capture that can judge
|
||||||
|
avatar detail (`--portrait-type=N` picks a role). For the 0.9.5 texture audit, run
|
||||||
|
the surface stages directly; the HUD is hidden for them:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
godot --path . --script res://src/test/capture_realism.gd \
|
||||||
|
-- --out=res://tmp/surfaces --stages=surfaces,surfaces_joke
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated realism runs under
|
||||||
|
`docs/screens/realism/` are **local, gitignored QA artifacts**; commit only the
|
||||||
|
resulting decisions/docs, not the run folders. The 0.9.5 texture/UI audit checked actual screen sizes: all screen art is
|
||||||
|
1280×720, access cards are 256×128 and HUD icons are 64×64; no runtime texture
|
||||||
|
was oversized or unused. The 24-NPC smoke now reports 168 meshes / 131,616
|
||||||
|
triangles / 6.94 ms. Compressed payloads are 24.55 MB of surface picks, 22.67 MB
|
||||||
|
of screen art and 0.48 MB of NPC maps.
|
||||||
|
|
||||||
|
The 0.9.5 realism judge ran once per shot with `z-ai/glm-5.3-flash`
|
||||||
|
(`python3 tools/scene_concepts.py realism --run docs/screens/realism/2026-11-02-tex
|
||||||
|
--judge`). Its mean `after` scores were overall 1.8, lighting 2.3, materials 1.8,
|
||||||
|
texture detail 1.2, backdrop 2.0 and characters 1.2; the Pillow metric gap was
|
||||||
|
0.306. The judge still ranks the painted concept targets above the stylized build,
|
||||||
|
particularly lit night facades and dense backdrop detail. That is expected: the
|
||||||
|
pass validated close-range surfaces and promoted the texture repairs, not a
|
||||||
|
photoreal renderer. The 0.9.5 before/after close-up sheet is
|
||||||
|
[`blender_previews/sheet_texture_refinement_0.9.5.jpg`](blender_previews/sheet_texture_refinement_0.9.5.jpg);
|
||||||
|
the texture-adoption run itself was the earlier 0.8-style candidate comparison,
|
||||||
|
and its report remains local/gitignored.
|
||||||
|
|
||||||
|
For the 0.9.6 integration checkpoint the same judge was pinned to
|
||||||
|
`z-ai/glm-5.3-flash` and run once per shot on the 10-shot set: mean overall 2.1,
|
||||||
|
lighting 2.4, materials 2.1, texture 1.7, backdrop 2.3, characters 1.8, Pillow
|
||||||
|
gap 0.291. No `review` verdicts were produced by the screen judge, so there was
|
||||||
|
no manual layout verdict to convert into a fix; the remaining realism comments
|
||||||
|
are the accepted stylized gaps.
|
||||||
|
|
||||||
|
Build release artifacts from a clean checkout or detached worktree. A normal
|
||||||
|
working tree may contain ignored `tmp/` captures and old `build/` artifacts, and
|
||||||
|
Godot can pack enough of those into exports to inflate a 0.9.6 package from
|
||||||
|
~160 MB to 1.6–3.1 GB. The 0.9.6 clean worktree produced 161.5 MB Linux,
|
||||||
|
197.1 MB Windows, 148.0 MB macOS, 97.8 MB Web and 241.5 MB Desktop HQ.
|
||||||
|
|
||||||
|
The Forward+ A/B compared the Compatibility entrance with the optional Desktop HQ
|
||||||
|
preset. The gear-fit run checked the sunglasses, bag and helmet against measured
|
||||||
|
head/skirt bounds with `python3 tools/blender_assets.py fit`; it also fixed the
|
||||||
|
portrait yaw and hid the focus overlay so the close-up shows the avatar.
|
||||||
|
|
||||||
|
Regenerate a run locally with the capture and `realism --run ...` commands above;
|
||||||
|
the reports and sheets are intentionally not part of the release repository.
|
||||||
|
|
||||||
## Screen design references
|
## Screen design references
|
||||||
|
|
||||||
@@ -241,6 +404,9 @@ python3 tools/screen_designs.py generate --draft --only wand --force
|
|||||||
# (requires OPENROUTER_API_KEY)
|
# (requires OPENROUTER_API_KEY)
|
||||||
python3 tools/screen_designs.py generate
|
python3 tools/screen_designs.py generate
|
||||||
|
|
||||||
|
# UI-state gallery (menu, pause, settings, review, radio, wheel, crowd, responder)
|
||||||
|
godot --path . --script res://src/test/capture_ui_states.gd -- --out=res://docs/screenshots
|
||||||
|
|
||||||
# Prop gallery (studio line-up) for close-range prop checks
|
# Prop gallery (studio line-up) for close-range prop checks
|
||||||
godot --path . --script res://src/test/capture_prop_gallery.gd
|
godot --path . --script res://src/test/capture_prop_gallery.gd
|
||||||
|
|
||||||
@@ -256,12 +422,11 @@ python3 tools/screen_designs.py judge --only 03- # one capture
|
|||||||
python3 tools/screen_designs.py judge --dry-run # build requests, no network
|
python3 tools/screen_designs.py judge --dry-run # build requests, no network
|
||||||
```
|
```
|
||||||
|
|
||||||
The catalogue covers 21 screens: the eleven README captures plus the start menu,
|
The catalogue covers 21 screens: the thirteen scene captures plus the start menu,
|
||||||
pause overlay, settings, shift review, radial menu, emergency radio, wand
|
pause overlay, settings, shift review, radial menu, emergency radio, crowd panic,
|
||||||
screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival,
|
and responder arrival. All 21 have an AI reference in `assets/images/ui/`
|
||||||
body-camera timeline, and the escalation HUD. All 21 have an AI reference in
|
(snapshot in `docs/screens/designs/`) and a committed capture, so every entry is
|
||||||
`assets/images/ui/` (snapshot in `docs/screens/designs/`); the eleven screens with
|
compared or judged against the running game.
|
||||||
staged captures are also compared or judged against the running game.
|
|
||||||
|
|
||||||
`compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank
|
`compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank
|
||||||
frame, and large layout/palette/detail distance. It is a regression gate, not a
|
frame, and large layout/palette/detail distance. It is a regression gate, not a
|
||||||
@@ -275,8 +440,8 @@ Verdicts are classed as `match`, `review` (a mismatch below `--fail-confidence`,
|
|||||||
default 0.6 — expected when a concept mockup is more stylised than the game), or
|
default 0.6 — expected when a concept mockup is more stylised than the game), or
|
||||||
`diff` (a confident mismatch, which exits non-zero). Regenerate the designs after changing screen layout, then re-run
|
`diff` (a confident mismatch, which exits non-zero). Regenerate the designs after changing screen layout, then re-run
|
||||||
`compare` after regenerating the game screenshots with
|
`compare` after regenerating the game screenshots with
|
||||||
`src/test/capture_screenshots.gd`.
|
`src/test/capture_screenshots.gd` and `src/test/capture_ui_states.gd`.
|
||||||
|
|
||||||
For a one-command drift check, run `tools/check_screenshots.sh`. It renders the
|
For a one-command drift check, run `tools/check_screenshots.sh`. It renders the
|
||||||
staged captures and prop gallery into `docs/screenshots/` (using `xvfb-run` when
|
scene captures, UI-state captures and prop gallery into `docs/screenshots/` (using
|
||||||
no display is available) and then runs `screen_designs.py compare`.
|
`xvfb-run` when no display is available) and then runs `screen_designs.py compare`.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# Release-candidate balance acceptance
|
||||||
|
|
||||||
|
These are the acceptance targets for the 0.10.0 balance pass, not measured results.
|
||||||
|
No reaction tables, event weights or scoring rules were changed to set them. The
|
||||||
|
[roadmap](../TODO.md) tracks the sweeps and external playtests still needed.
|
||||||
|
[Gameplay](gameplay.md) owns the mechanics; this page owns their balance-test protocol.
|
||||||
|
|
||||||
|
## Test matrix and evidence
|
||||||
|
|
||||||
|
Run Hollywood, Joke Shop and TV Studio on easy, normal and hard, in Early,
|
||||||
|
Midday, Evening and Late: **36 cells**. Use seeds 1–100 for each cell and retain
|
||||||
|
those seeds for before/after comparisons. Exercise each eligible authored event
|
||||||
|
at least once as a separate coverage pass; a random schedule can miss rare events.
|
||||||
|
Record scheduled event counts separately from forced coverage cases so the latter
|
||||||
|
do not distort event-frequency estimates.
|
||||||
|
|
||||||
|
For reaction measurements, use a fresh controller at the entrance for each sample.
|
||||||
|
Routine cases are tourists, confused visitors and delivery workers obstructing the
|
||||||
|
entrance without threatening behavior. Do not include authorized entry requests in
|
||||||
|
this redirect cohort. For peaceful-resolution measurements, send one polite
|
||||||
|
direction (L1), then one warning (L2) only if still unresolved. Do not reroll until
|
||||||
|
success. Observe return behavior for 60 simulated seconds before closing the case.
|
||||||
|
Do not issue emergency calls as part of this policy.
|
||||||
|
|
||||||
|
For access cases, follow the venue's required inspection and screening steps before
|
||||||
|
allow/deny. Include valid, expired, counterfeit, mismatched, missing and ambiguous
|
||||||
|
artifacts, known recognition, age boundaries, screening refusal, benign wand alerts
|
||||||
|
and prohibited items. Recognition must be independently justified by the fixture;
|
||||||
|
calling a stranger recognized is not evidence of correct admission. An unresolved
|
||||||
|
hold counts as unresolved, not as a correct refusal.
|
||||||
|
|
||||||
|
Evidence must identify the commit, engine, venue, difficulty, phase, seed, event ID,
|
||||||
|
cohort, visible evidence, policy action, reaction/state, return, access truth and
|
||||||
|
decision, emergency truth and call, report fields, reputation modifiers, final grade
|
||||||
|
and unresolved cases. Save raw rows plus numerator/denominator summaries for every
|
||||||
|
cell. Use at least **100 eligible cases per metric per cell**; if the schedule
|
||||||
|
provides fewer, add a separately labelled forced-case sample. Report zero eligible
|
||||||
|
cases as **N/A**, never as 100% success. Keep scheduled and forced results separate.
|
||||||
|
|
||||||
|
## Targets
|
||||||
|
|
||||||
|
Percentages below apply to each matrix cell, not only a pooled average. Scripted
|
||||||
|
access/report cases use complete evidence and are held to exact correctness;
|
||||||
|
first-time-player results measure whether that evidence is understandable.
|
||||||
|
|
||||||
|
| Metric | Numerator / denominator | Acceptance target |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| First-direction routine compliance | First L1 reaction is `COMPLY` or `LEAVE` / eligible routine redirect cases | Easy ≥80%; normal ≥75%; hard ≥65% |
|
||||||
|
| Routine peaceful resolution | Routine cases leaving after at most L1 then L2, with no return during 60 s and no L3–L5 / eligible routine redirect cases | Easy ≥90%; normal ≥85%; hard ≥80% |
|
||||||
|
| Access accuracy | Correct final allow/deny decisions / all eligible access cases (including unresolved cases) | Scripted 100%; first-time players ≥90% pooled, with each error reviewed |
|
||||||
|
| Access false positive | Unauthorized people allowed / unauthorized access cases | Scripted 0%; first-time players ≤5% pooled |
|
||||||
|
| Access false negative | Authorized people denied / authorized access cases | Scripted 0%; first-time players ≤10% pooled |
|
||||||
|
| Emergency precision | Calls supported by immediate-danger evidence / all emergency calls | Scripted 100%; first-time players ≥95% pooled; zero calls is N/A |
|
||||||
|
| Emergency false positive | Routine/no-immediate-danger cases receiving an emergency call / all routine/no-immediate-danger cases | Scripted 0%; first-time players ≤5% pooled |
|
||||||
|
| Emergency false negative | Immediate-danger cases without an accepted emergency call within 15 simulated seconds of visible danger / all immediate-danger cases with radio initially available | Scripted 0%; first-time players ≤10% pooled |
|
||||||
|
| Report accuracy | Reports with correct weapon and danger fields / all submitted reports | Scripted 100%; first-time players ≥90% pooled |
|
||||||
|
|
||||||
|
For access, authorized means entitled under the venue policy **after required
|
||||||
|
screening**, without a prohibited item or an independent immediate-danger reason
|
||||||
|
to refuse. Compare to a hand-labelled fixture expectation, not only to
|
||||||
|
`AccessVerifier.verify()`: using the verifier as both decision and oracle would
|
||||||
|
hide policy bugs. Report unresolved access cases separately; they cannot improve
|
||||||
|
either error rate by being omitted from accuracy.
|
||||||
|
|
||||||
|
Immediate danger means an observed weapon, assault, active forced entry or a
|
||||||
|
credible immediate threat to occupants. Argument, filming, intoxication, a crisis,
|
||||||
|
a refused screen or a benign wand alert alone is not immediate danger. A mental
|
||||||
|
health label or clothing must never be used as the emergency oracle. The 15 s
|
||||||
|
window is a test-response target, not the responder-arrival time (currently 15 s
|
||||||
|
after an accepted emergency call). Separately test depleted battery and active
|
||||||
|
cooldown cases for safe containment/assistance; do not silently drop them from the
|
||||||
|
evidence or claim the initially-available-radio target covers them.
|
||||||
|
|
||||||
|
## Scoring and emergency dominance
|
||||||
|
|
||||||
|
Compare the calm policy with an emergency-first policy on the same routine events
|
||||||
|
and seeds. Each comparison starts with fresh radio, crowd and reputation state.
|
||||||
|
An accurate report about harmless behavior must not make its emergency call
|
||||||
|
justified. Require an unjustified-call investigation and a strictly lower rating
|
||||||
|
for emergency-first in every routine comparison. Inspect modifiers as well as the
|
||||||
|
final rating: the 0–100 clamp can hide rewards/penalties. Emergency-first must not
|
||||||
|
improve the campaign outcome or final grade. Grade boundaries remain A ≥90,
|
||||||
|
B ≥75, C ≥60, D ≥40, otherwise F; these are existing rules, not new tuning.
|
||||||
|
|
||||||
|
In genuine-danger cases, verify that the call is accepted, responders arrive,
|
||||||
|
the incident remains answerable and the debrief explains the outcome without an
|
||||||
|
unjustified-call investigation. Also compare assistance/containment routes where
|
||||||
|
appropriate; emergency need not be mandatory for every difficult interaction.
|
||||||
|
|
||||||
|
## Implementation cautions and sign-off
|
||||||
|
|
||||||
|
- `NPCController.roll_reaction()` samples per-level weights. Difficulty adds
|
||||||
|
+12/0/−8 to the `COMPLY` weight before normalization; `base_compliance` alone
|
||||||
|
is not the observed compliance percentage. Measure `COMPLY` plus `LEAVE`.
|
||||||
|
- `GameManager._read_npc_truth()` currently derives weapon/danger truth from the
|
||||||
|
NPC type (violent threat: weapon/3; everyone else: no weapon/1), rather than
|
||||||
|
observed behavior. This is a balance-review risk: test nonviolent profiles that
|
||||||
|
threaten or force entry, and violent profiles before visible danger. Do not
|
||||||
|
treat agreement with this helper as proof of fair emergency judgment.
|
||||||
|
- `RadioSystem.credibility_hits()` checks weapon and danger fields only;
|
||||||
|
location, description and behavior still need human review. Difficulty scales
|
||||||
|
report penalties, not the oracle.
|
||||||
|
- A terminal peaceful reaction is not sufficient evidence of an incident staying
|
||||||
|
resolved: returning NPCs and unresolved shift-review entries must be retained.
|
||||||
|
|
||||||
|
Do not tune to a pooled average that hides a failing venue, phase or difficulty.
|
||||||
|
Record each miss, its severity, the raw evidence and any approved exception before
|
||||||
|
changing weights. Re-run the affected cells and tutorial/campaign smokes after a
|
||||||
|
fix. Five first-time-player sessions must cover the tutorial and a complete venue
|
||||||
|
shift; report pooled counts and per-session errors, not statistical confidence
|
||||||
|
from five players. Scripted sweeps do not substitute for those sessions.
|
||||||
|
|
||||||
|
**Status:** targets defined; sweeps, emergency fairness review, grade/campaign
|
||||||
|
verification and external-player acceptance remain pending. No balance sign-off
|
||||||
|
or release approval is implied by this document.
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
# Blender asset library
|
# Blender asset library
|
||||||
|
|
||||||
|
Architecture context: the asset contracts and fallback path are summarized in
|
||||||
|
[`architecture.md`](architecture.md#content-and-asset-pipeline-architecture).
|
||||||
|
|
||||||
Status: built, audited, and integrated (2026-11-01). `NpcAvatar` selects the
|
Status: built, audited, and integrated (2026-11-01). `NpcAvatar` selects the
|
||||||
character variants and attaches the gear; `WorldBuilder` loads the prop library
|
character variants and attaches the gear; `WorldBuilder` loads the prop library
|
||||||
with colliders and phase-driven emissives. See the Phase 3.5 integration in
|
with colliders and phase-driven emissives. The 0.9.4 neutral baseline and its
|
||||||
[`plan-graphics-v0.6.md`](plan-graphics-v0.6.md).
|
acceptance criteria are in [`npc-visual-review.md`](npc-visual-review.md);
|
||||||
|
the committed 8 × 6 contact sheet is
|
||||||
|
[`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg).
|
||||||
|
|
||||||
Generator: [`tools/blender_assets.py`](../tools/blender_assets.py). Manifest (the
|
Generator: [`tools/blender_assets.py`](../tools/blender_assets.py). Manifest (the
|
||||||
source of truth for integration):
|
source of truth for integration):
|
||||||
@@ -43,6 +48,9 @@ python3 tools/blender_assets.py sheet # -> docs/blender_previews/sheet_<catego
|
|||||||
python3 tools/blender_assets.py audit # stdlib only; exit 1 on any issue
|
python3 tools/blender_assets.py audit # stdlib only; exit 1 on any issue
|
||||||
python3 tools/blender_assets.py fit # head/clothing fit + bag-body clearance, per character
|
python3 tools/blender_assets.py fit # head/clothing fit + bag-body clearance, per character
|
||||||
python3 tools/blender_assets.py list
|
python3 tools/blender_assets.py list
|
||||||
|
# import a GLB, apply node transforms, report geometry/bones/UVs/skinning + contract failures
|
||||||
|
flatpak run org.blender.Blender --background \
|
||||||
|
--python "$PWD/tools/blender_assets.py" -- inspect --file "$PWD/tmp/downloads/x.glb"
|
||||||
godot --headless --path . --import # after any rebuild
|
godot --headless --path . --import # after any rebuild
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -85,12 +93,16 @@ All eight use the `npc_base` contract, so each one can replace
|
|||||||
`test_character_joints_line_up_with_npc_base` checks this within 1 cm, so the
|
`test_character_joints_line_up_with_npc_base` checks this within 1 cm, so the
|
||||||
animation proxies in `_build_skinned_body()` stay valid.
|
animation proxies in `_build_skinned_body()` stay valid.
|
||||||
- material slots `skin`, `cloth`, `pants`, `accessory`, `hair` carry the same
|
- material slots `skin`, `cloth`, `pants`, `accessory`, `hair` carry the same
|
||||||
neutral `assets/textures/npc/<slot>_{albedo,normal}.png` maps, so
|
neutral `assets/textures/npc/<slot>_{albedo,normal}.png` maps at 256², so
|
||||||
`Materials.npc_slot()` tints them the same way it tints `npc_base`.
|
`Materials.npc_slot()` tints them the same way it tints `npc_base`. Cloth and
|
||||||
- budget ≤ 6000 triangles and ≤ 5000 vertices (glTF vertex count, after seam
|
pants repeat the weave 4× (`NPC_FABRIC_UV_SCALE`) and intentionally skip the
|
||||||
splits; `CHAR_MAX_VERTS` in `tools/blender_assets.py`, raised from 4000 for
|
per-character baked AO; skin, hair and fixed gear keep it.
|
||||||
the 2026-11-01 face/hand pass). The current range is 3,974–5,026 triangles
|
- budget ≤ **8,000 triangles and ≤ 8,000 glTF vertices** for the eight library
|
||||||
and 3,744–4,807 vertices.
|
variants (vertex count after seam splits; `CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in
|
||||||
|
`tools/blender_assets.py`, raised from 6,000/5,000 on 2026-11-02). The frozen
|
||||||
|
`npc_base` fallback keeps its original 6,000/4,000 contract. The current range
|
||||||
|
is 4,482–5,474 triangles and 4,260–5,494 vertices, leaving room for the
|
||||||
|
remaining grip and clothing detail.
|
||||||
|
|
||||||
Construction: the torso and limbs are one continuous Skin-modifier mesh,
|
Construction: the torso and limbs are one continuous Skin-modifier mesh,
|
||||||
subdivided twice, decimated to 26 % with X symmetry, and cut at the waist, cuffs,
|
subdivided twice, decimated to 26 % with X symmetry, and cut at the waist, cuffs,
|
||||||
@@ -139,6 +151,10 @@ attach point:
|
|||||||
|
|
||||||
The character previews in `sheet_npc.jpg` are built by importing the gear at
|
The character previews in `sheet_npc.jpg` are built by importing the gear at
|
||||||
exactly these points, which gives a quick visual check of the convention.
|
exactly these points, which gives a quick visual check of the convention.
|
||||||
|
`NpcAvatar.GEAR_OFFSETS` applies two final-context nudges after attachment:
|
||||||
|
the lanyard moves 4 cm forward so its badge clears the jacket, and the
|
||||||
|
firefighter helmet moves 3 cm up so its brim sits above the eyes. The
|
||||||
|
`fit` report remains an asset-space measurement of the untouched GLBs.
|
||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
@@ -159,16 +175,16 @@ exactly these points, which gives a quick visual check of the convention.
|
|||||||
| `marquee` | the procedural marquee | 6 m canopy and sign box; emissive `bulb` + `sign_face`. Overlay the text with the existing `Label3D` |
|
| `marquee` | the procedural marquee | 6 m canopy and sign box; emissive `bulb` + `sign_face`. Overlay the text with the existing `Label3D` |
|
||||||
| `sedan` | new (studio arrival) | 4.6 m; emissive `headlight`, red tail lamps |
|
| `sedan` | new (studio arrival) | 4.6 m; emissive `headlight`, red tail lamps |
|
||||||
|
|
||||||
Props use plain PBR factors (base colour, roughness, metallic, emission) and
|
Props use plain PBR factors (base colour, roughness, metallic, emission) plus
|
||||||
have no textures. They are not yet AO-baked; that is still a Phase 4 task.
|
the committed 256² baked AO maps (`<name>_ao.png`) wired by `WorldBuilder`;
|
||||||
Emissive materials are always on in the GLB. The integration should tie them to
|
emissive materials are tied to `WorldBuilder.set_night_mode()`. Characters
|
||||||
`WorldBuilder.set_night_mode()`.
|
carry baked AO and the shared neutral material maps described above.
|
||||||
|
|
||||||
## Checks
|
## Checks
|
||||||
|
|
||||||
- `python3 tools/blender_assets.py audit`: container, `POSITION`/`NORMAL`/
|
- `python3 tools/blender_assets.py audit`: container, `POSITION`/`NORMAL`/
|
||||||
`TEXCOORD_0`, UVs in [0, 1], no zero-area triangles (Godot warns about them
|
`TEXCOORD_0`, UVs in [0, 1], no zero-area triangles (Godot warns about them
|
||||||
during LOD generation), budgets (characters 6000/5000, gear 1500, props 4000
|
during LOD generation), budgets (library characters 8000/8000, base fallback 6000/4000, gear 1500, props 4000
|
||||||
triangles), grounding, and an editable `.blend`. For characters it also
|
triangles), grounding, and an editable `.blend`. For characters it also
|
||||||
checks skinning, bones, slots, the `npc_base` mesh node and the height.
|
checks skinning, bones, slots, the `npc_base` mesh node and the height.
|
||||||
- `tools/test_blender_assets.py`: catalogue and contract consistency with
|
- `tools/test_blender_assets.py`: catalogue and contract consistency with
|
||||||
@@ -191,3 +207,8 @@ Emissive materials are always on in the GLB. The integration should tie them to
|
|||||||
the legs through the hem.
|
the legs through the hem.
|
||||||
- Preview colours (`PREVIEW_TINTS`) only stand in for `outfit_for()`. The GLBs
|
- Preview colours (`PREVIEW_TINTS`) only stand in for `outfit_for()`. The GLBs
|
||||||
stay neutral.
|
stay neutral.
|
||||||
|
- The 0.9.4 baseline found three P1 portrait issues. The tourist's photo prop
|
||||||
|
now only appears in photo/film poses and the backpack straps were refit as
|
||||||
|
flattened chest bands; the face is still shallow/faceted at portrait range.
|
||||||
|
The full ranked list, measurements and before/after sheet are in
|
||||||
|
[`npc-visual-review.md`](npc-visual-review.md).
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Blender Previews
|
||||||
|
|
||||||
|
Committed contact sheets and local Blender renders for the character, gear and prop library. Individual PNGs are development previews and are gitignored; the sheets are tracked.
|
||||||
|
|
||||||
|
## Image previews
|
||||||
|
|
||||||
|
| Preview | File |
|
||||||
|
| --- | --- |
|
||||||
|
| <img src="a_frame_sign.png" width="280" alt="a_frame_sign.png"> | [`a_frame_sign.png`](a_frame_sign.png) |
|
||||||
|
| <img src="backpack.png" width="280" alt="backpack.png"> | [`backpack.png`](backpack.png) |
|
||||||
|
| <img src="beanie.png" width="280" alt="beanie.png"> | [`beanie.png`](beanie.png) |
|
||||||
|
| <img src="bench.png" width="280" alt="bench.png"> | [`bench.png`](bench.png) |
|
||||||
|
| <img src="bollard.png" width="280" alt="bollard.png"> | [`bollard.png`](bollard.png) |
|
||||||
|
| <img src="camera.png" width="280" alt="camera.png"> | [`camera.png`](camera.png) |
|
||||||
|
| <img src="cap.png" width="280" alt="cap.png"> | [`cap.png`](cap.png) |
|
||||||
|
| <img src="clipboard.png" width="280" alt="clipboard.png"> | [`clipboard.png`](clipboard.png) |
|
||||||
|
| <img src="crowd_barrier.png" width="280" alt="crowd_barrier.png"> | [`crowd_barrier.png`](crowd_barrier.png) |
|
||||||
|
| <img src="cup.png" width="280" alt="cup.png"> | [`cup.png`](cup.png) |
|
||||||
|
| <img src="double_door.png" width="280" alt="double_door.png"> | [`double_door.png`](double_door.png) |
|
||||||
|
| <img src="fire_hydrant.png" width="280" alt="fire_hydrant.png"> | [`fire_hydrant.png`](fire_hydrant.png) |
|
||||||
|
| <img src="firefighter_helmet.png" width="280" alt="firefighter_helmet.png"> | [`firefighter_helmet.png`](firefighter_helmet.png) |
|
||||||
|
| <img src="flashlight.png" width="280" alt="flashlight.png"> | [`flashlight.png`](flashlight.png) |
|
||||||
|
| <img src="handrail.png" width="280" alt="handrail.png"> | [`handrail.png`](handrail.png) |
|
||||||
|
| <img src="hood.png" width="280" alt="hood.png"> | [`hood.png`](hood.png) |
|
||||||
|
| <img src="lanyard.png" width="280" alt="lanyard.png"> | [`lanyard.png`](lanyard.png) |
|
||||||
|
| <img src="marquee.png" width="280" alt="marquee.png"> | [`marquee.png`](marquee.png) |
|
||||||
|
| <img src="newspaper_box.png" width="280" alt="newspaper_box.png"> | [`newspaper_box.png`](newspaper_box.png) |
|
||||||
|
| <img src="npc_casual.png" width="280" alt="npc_casual.png"> | [`npc_casual.png`](npc_casual.png) |
|
||||||
|
| <img src="npc_coat.png" width="280" alt="npc_coat.png"> | [`npc_coat.png`](npc_coat.png) |
|
||||||
|
| <img src="npc_dress.png" width="280" alt="npc_dress.png"> | [`npc_dress.png`](npc_dress.png) |
|
||||||
|
| <img src="npc_firefighter.png" width="280" alt="npc_firefighter.png"> | [`npc_firefighter.png`](npc_firefighter.png) |
|
||||||
|
| <img src="npc_guard.png" width="280" alt="npc_guard.png"> | [`npc_guard.png`](npc_guard.png) |
|
||||||
|
| <img src="npc_hoodie.png" width="280" alt="npc_hoodie.png"> | [`npc_hoodie.png`](npc_hoodie.png) |
|
||||||
|
| <img src="npc_jacket.png" width="280" alt="npc_jacket.png"> | [`npc_jacket.png`](npc_jacket.png) |
|
||||||
|
| <img src="npc_vest.png" width="280" alt="npc_vest.png"> | [`npc_vest.png`](npc_vest.png) |
|
||||||
|
| <img src="palm_tree.png" width="280" alt="palm_tree.png"> | [`palm_tree.png`](palm_tree.png) |
|
||||||
|
| <img src="phone.png" width="280" alt="phone.png"> | [`phone.png`](phone.png) |
|
||||||
|
| <img src="planter_box.png" width="280" alt="planter_box.png"> | [`planter_box.png`](planter_box.png) |
|
||||||
|
| <img src="protest_sign.png" width="280" alt="protest_sign.png"> | [`protest_sign.png`](protest_sign.png) |
|
||||||
|
| <img src="radio.png" width="280" alt="radio.png"> | [`radio.png`](radio.png) |
|
||||||
|
| <img src="round_planter.png" width="280" alt="round_planter.png"> | [`round_planter.png`](round_planter.png) |
|
||||||
|
| <img src="scba_tank.png" width="280" alt="scba_tank.png"> | [`scba_tank.png`](scba_tank.png) |
|
||||||
|
| <img src="security_podium.png" width="280" alt="security_podium.png"> | [`security_podium.png`](security_podium.png) |
|
||||||
|
| <img src="sedan.png" width="280" alt="sedan.png"> | [`sedan.png`](sedan.png) |
|
||||||
|
| <img src="sheet_npc.jpg" width="280" alt="sheet_npc.jpg"> | [`sheet_npc.jpg`](sheet_npc.jpg) |
|
||||||
|
| <img src="sheet_npc_sources_research.jpg" width="280" alt="sheet_npc_sources_research.jpg"> | [`sheet_npc_sources_research.jpg`](sheet_npc_sources_research.jpg) |
|
||||||
|
| <img src="sheet_npc_crowd_prototypes.jpg" width="280" alt="sheet_npc_crowd_prototypes.jpg"> | [`sheet_npc_crowd_prototypes.jpg`](sheet_npc_crowd_prototypes.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline.jpg" width="280" alt="sheet_npc_baseline.jpg"> | [`sheet_npc_baseline.jpg`](sheet_npc_baseline.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline_compare-p1.jpg" width="280" alt="sheet_npc_baseline_compare-p1.jpg"> | [`sheet_npc_baseline_compare-p1.jpg`](sheet_npc_baseline_compare-p1.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline_compare-body.jpg" width="280" alt="sheet_npc_baseline_compare-body.jpg"> | [`sheet_npc_baseline_compare-body.jpg`](sheet_npc_baseline_compare-body.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline_compare-face.jpg" width="280" alt="sheet_npc_baseline_compare-face.jpg"> | [`sheet_npc_baseline_compare-face.jpg`](sheet_npc_baseline_compare-face.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline_compare-torso.jpg" width="280" alt="sheet_npc_baseline_compare-torso.jpg"> | [`sheet_npc_baseline_compare-torso.jpg`](sheet_npc_baseline_compare-torso.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline_compare-face-detail.jpg" width="280" alt="sheet_npc_baseline_compare-face-detail.jpg"> | [`sheet_npc_baseline_compare-face-detail.jpg`](sheet_npc_baseline_compare-face-detail.jpg) |
|
||||||
|
| <img src="sheet_npc_hands_compare.jpg" width="280" alt="sheet_npc_hands_compare.jpg"> | [`sheet_npc_hands_compare.jpg`](sheet_npc_hands_compare.jpg) |
|
||||||
|
| <img src="sheet_npc_hands_grip_compare.jpg" width="280" alt="sheet_npc_hands_grip_compare.jpg"> | [`sheet_npc_hands_grip_compare.jpg`](sheet_npc_hands_grip_compare.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline_compare-shoes.jpg" width="280" alt="sheet_npc_baseline_compare-shoes.jpg"> | [`sheet_npc_baseline_compare-shoes.jpg`](sheet_npc_baseline_compare-shoes.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline_compare-tex256.jpg" width="280" alt="sheet_npc_baseline_compare-tex256.jpg"> | [`sheet_npc_baseline_compare-tex256.jpg`](sheet_npc_baseline_compare-tex256.jpg) |
|
||||||
|
| <img src="sheet_npc_baseline_compare-texuv.jpg" width="280" alt="sheet_npc_baseline_compare-texuv.jpg"> | [`sheet_npc_baseline_compare-texuv.jpg`](sheet_npc_baseline_compare-texuv.jpg) |
|
||||||
|
| <img src="sheet_texture_refinement_0.9.5.jpg" width="280" alt="sheet_texture_refinement_0.9.5.jpg"> | [`sheet_texture_refinement_0.9.5.jpg`](sheet_texture_refinement_0.9.5.jpg) |
|
||||||
|
| <img src="sheet_npc_gear.jpg" width="280" alt="sheet_npc_gear.jpg"> | [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) |
|
||||||
|
| <img src="sheet_props.jpg" width="280" alt="sheet_props.jpg"> | [`sheet_props.jpg`](sheet_props.jpg) |
|
||||||
|
| <img src="shoulder_bag.png" width="280" alt="shoulder_bag.png"> | [`shoulder_bag.png`](shoulder_bag.png) |
|
||||||
|
| <img src="stanchion.png" width="280" alt="stanchion.png"> | [`stanchion.png`](stanchion.png) |
|
||||||
|
| <img src="street_lamp.png" width="280" alt="street_lamp.png"> | [`street_lamp.png`](street_lamp.png) |
|
||||||
|
| <img src="street_tree.png" width="280" alt="street_tree.png"> | [`street_tree.png`](street_tree.png) |
|
||||||
|
| <img src="sunglasses.png" width="280" alt="sunglasses.png"> | [`sunglasses.png`](sunglasses.png) |
|
||||||
|
| <img src="traffic_cone.png" width="280" alt="traffic_cone.png"> | [`traffic_cone.png`](traffic_cone.png) |
|
||||||
|
| <img src="trash_bin.png" width="280" alt="trash_bin.png"> | [`trash_bin.png`](trash_bin.png) |
|
||||||
|
| <img src="umbrella.png" width="280" alt="umbrella.png"> | [`umbrella.png`](umbrella.png) |
|
||||||
|
| <img src="wall_lantern.png" width="280" alt="wall_lantern.png"> | [`wall_lantern.png`](wall_lantern.png) |
|
||||||
|
| <img src="wand.png" width="280" alt="wand.png"> | [`wand.png`](wand.png) |
|
||||||
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 350 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 482 KiB |
@@ -1,11 +1,17 @@
|
|||||||
# Development Status
|
# Development Status
|
||||||
|
|
||||||
|
Current planning priorities are the unversioned **3D model re-evaluation**, **Character
|
||||||
|
development** and **Local map development** tracks in [`TODO.md`](../TODO.md); none is part of the
|
||||||
|
implemented list below yet. The re-evaluation scores the existing characters, gear, props and
|
||||||
|
venues against the locally installed Blender skill guidance before the roster expands. The 0.10.0
|
||||||
|
release candidate remains the next versioned release.
|
||||||
|
|
||||||
Implemented so far:
|
Implemented so far:
|
||||||
|
|
||||||
- Godot 4.3 project scaffold, input map, procedural world builder
|
- Godot 4.7.2 project scaffold, input map, procedural world builder (CI/local/release unified)
|
||||||
- Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`
|
- Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`
|
||||||
- Three authored scenarios (Hollywood Boulevard building, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools
|
- Three authored scenarios (El Capitan Entertainment Centre / Hollywood Boulevard, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools
|
||||||
- Headless unit tests: **231 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings)
|
- Headless unit tests: **272 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings)
|
||||||
- Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event
|
- Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event
|
||||||
- First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review
|
- First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review
|
||||||
- Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`)
|
- Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`)
|
||||||
@@ -38,12 +44,51 @@ Implemented so far:
|
|||||||
- Web build: `build/gig-line-web-<version>.zip` (Godot's non-threaded web template, no COOP/COEP headers needed) built by `tools/build_releases.sh` and published with the desktop artifacts; serve the unzipped folder from any static host
|
- Web build: `build/gig-line-web-<version>.zip` (Godot's non-threaded web template, no COOP/COEP headers needed) built by `tools/build_releases.sh` and published with the desktop artifacts; serve the unzipped folder from any static host
|
||||||
- Aim-free selection pass (v0.3.0 work, [ADR-0011](adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work
|
- Aim-free selection pass (v0.3.0 work, [ADR-0011](adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work
|
||||||
|
|
||||||
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing.
|
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. The wand panel lists the guest's carried items, and multiline HUD/wand strings render real line breaks.
|
||||||
- Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a visible additive flashlight beam, and refreshed staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts.
|
- NPC neck/shoulder transition (0.9.4 work): the body graph now uses a fuller neck base, a slightly tapered neck top and a lower shoulder root, giving the head/neck/shoulder line a trapezius slope instead of a thin post. The full character library was rebuilt with a final degenerate-face cleanup; the baseline comparison is `sheet_npc_baseline_compare-body.jpg`.
|
||||||
- Input/reliability fixes: selecting a venue now releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status.
|
- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
|
||||||
|
- NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`.
|
||||||
|
- Release v0.9.6 (2026-11-03): the tagged commit `4bb2896` passed Gitea Actions run 1296 (checks + full build) and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.6>.
|
||||||
|
- Release candidate (0.10.0-rc.1, 2026-11-03): clean worktree `a9658ec` built Linux 161.5 MB, Windows 197.2 MB, macOS 148.0 MB, Web 97.8 MB and Desktop HQ 241.6 MB. The Web/HQ archives contain `CREDITS.md`, `PRIVACY.md`, `docs/accessibility.md` and `docs/known-limitations.md`; SHA-256 checksums were generated, the Linux and HQ binaries launched headless, and the Web index/wasm/pck/docs served with HTTP 200. The candidate is not published: external playtest, AI-provider/legal approval, macOS signing and the Windows/macOS target matrix remain open blockers.
|
||||||
|
- Save migration and reliability (0.10.0 work): committed 0.8-era settings/campaign/history fixtures and a 0.9.0 checkpoint. The loader default-fills old settings, keeps campaign progress and validates checkpoints for all three venues with an open incident, NPC rows, door, flashlight and body-camera state. Corrupt and partial files fall back to defaults; a blocked atomic replace leaves the prior valid file intact. `_load_json` uses the non-logging JSON parser; save locations and rollback are documented in `getting-started.md`. Suite is 272/272;
|
||||||
|
Python tooling is 108/108 after the GLB `inspect` intake coverage.
|
||||||
|
- Integration checkpoint (0.9.6 work): the clean worktree at `f5b8ef2` rebuilt all five release packages and the Desktop HQ zip without ignored `tmp/`/`build/` content (Linux 161.5 MB, Windows 197.1 MB, macOS 148.0 MB, Web 97.8 MB, HQ 241.5 MB). The Web zip contains `index.html`, `index.wasm` and `index.pck`; the HQ zip contains `override.cfg` and launches headless with the Forward+ override. Current verification: Godot 263/263, Python 101/101, 14 smokes green, 24-NPC budget 6.90 ms / 168 meshes / 131,616 triangles, gallery 21 good / 0 bad, screen judge 21/21 match with 0 review/diff, and a pinned realism judge (`z-ai/glm-5.3-flash`, one pass per shot) at mean overall 2.1 and Pillow gap 0.291. No new gameplay or content changes.
|
||||||
|
- Release v0.9.5 (2026-11-02): the tagged commit `4bc6390` passed Gitea Actions run 922 (checks + full build) and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.5>.
|
||||||
|
- Release v0.9.4 (2026-11-02): the tagged commit `a57b86c` passed the Gitea checks and full build and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4>.
|
||||||
|
- Environment texture pass (0.9.5 work): `capture_realism.gd` now has hidden-HUD `surfaces`/`surfaces_joke` close-up stages. The Walk of Fame band uses the paver texture as dark terrazzo, the neighbour awning keeps its cream stripe, and the skyline shader blends its lower 42 m toward a per-phase haze colour. Brick, asphalt, limestone and paver UV scales/profiles passed without replacement.
|
||||||
|
- Texture/prop audit (0.9.5 work): all 21 screen-art textures are 1280×720 and referenced; access cards are 256×128 and HUD icons 64×64. No oversized or unused runtime texture was found; the legacy root textures remain only as the texture-generator/asset-test fallback. The 24-NPC smoke now reports 168 meshes, 131,616 triangles and 6.94 ms (50 ms budget); compressed payload is 24.55 MB of surface picks, 22.67 MB of screen art and 0.48 MB of NPC maps.
|
||||||
|
- NPC texture pass (0.9.5 work): the NPC base and all eight library characters now use 256² albedo/normal/ORM maps instead of 128². Cloth and pants use `NPC_FABRIC_UV_SCALE` (4× weave) and skip the baked per-character AO, removing the large blotchy patches in portraits; skin, hair and gear keep baked AO. The base and eight GLBs plus AO maps were rebuilt; the 24-NPC smoke remains green.
|
||||||
|
- Texture visual verification (0.9.5 work): the nine-stage realism run used `z-ai/glm-5.3-flash` once per shot; mean `after` scores were overall 1.8, lighting 2.3, materials 1.8, texture detail 1.2, backdrop 2.0 and characters 1.2, with a Pillow metric gap of 0.306. The judge remains an optional review aid, not a CI gate. The committed before/after close-up sheet is [`blender_previews/sheet_texture_refinement_0.9.5.jpg`](blender_previews/sheet_texture_refinement_0.9.5.jpg); it records that the paver/Walk of Fame and NPC fabric repairs are visible, while asphalt, limestone and skyline picks held without a replacement. `tools/check_screenshots.sh` passed 21 good / 0 bad and the screen-design judge passed 21/21 match with 0 review.
|
||||||
|
- NPC visual optimization (0.9.4, complete 2026-11-02): all eight variants passed the portrait/readability, proportion, gear, foot/shoe, headgear and final-context gates. The library uses an 8,000-triangle / 8,000-glTF-vertex cap (up from 6,000/5,000; `npc_base` fallback remains 6,000/4,000); current characters measure 4,482–5,474 triangles and 4,260–5,494 vertices. The 48-frame baseline and 96-frame final-context review pass, the hand/gear offset and shoes are in, and the accepted stylized limits are documented in `npc-visual-review.md`.
|
||||||
|
- NPC face-detail pass (0.9.4 work): ala wings and a smaller nose bridge/tip, one rounded mouth slot, and a darker ear concha; all eight rebuilt. The comparison is `sheet_npc_baseline_compare-face-detail.jpg`.
|
||||||
|
- NPC headgear clearance (0.9.4 work): the 96-frame final-context review after the face-detail pass confirms the cap, firefighter helmet, hood and sunglasses clear the refined brow and eyes; no gear rebuild was needed.
|
||||||
|
- NPC footwear pass (0.9.4 work): casual shoes gained rounded toe and heel caps; responder/firefighter boots gained matching toe/heel volumes while retaining the black rubber boot language. All eight rebuilt; comparison `sheet_npc_baseline_compare-shoes.jpg`.
|
||||||
|
- NPC hand-item grip (0.9.4 work): runtime `HAND_ITEM_FORWARD` moves every hand gear 4 cm forward from the wrist proxy, so phone, cup, clipboard, camera, flashlight, wand and radio sit in the open palm instead of partly inside it. The open-hand/no-finger-rig limit is accepted for this stylized release; the comparison is `sheet_npc_hands_grip_compare.jpg`.
|
||||||
|
- NPC hand pass (0.9.4 work): wider, shallower palms; four two-segment bent fingers with rounded tips and a separate bent thumb; optional `--view=hand` capture at 0.80 m. The hand comparison is `sheet_npc_hands_compare.jpg`; the forward offset is in and the open-palm limit is accepted.
|
||||||
|
- NPC proportion audit (0.9.4 work): `tools/npc_proportions.py` measures all eight GLBs against a stylized height/head/shoulder/hip/torso/leg/arm/foot guide. All variants pass; the head and broad-build silhouettes are the nearest upper bounds. Results are in [`npc-proportion-audit.md`](npc-proportion-audit.md).
|
||||||
|
- NPC P1 portrait fixes (0.9.4 work): tourists now hide their photo-pose camera bag outside `photo`/`film` poses, removing the duplicate-backpack silhouette; the backpack gear was rebuilt with flattened, forward chest straps and a fresh AO map. The before/after sheet is [`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); the remaining face/hand work was closed under the 0.9.4 acceptance limits.
|
||||||
|
- NPC visual baseline (0.9.4 work): `src/test/capture_npc_baseline.gd` captures all eight library characters from front, three-quarter, profile, back, full-body and crowd views in a neutral studio (48 frames). The criteria, measurements and ranked gaps are in [`npc-visual-review.md`](npc-visual-review.md); the committed sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg). No model changes were made in this baseline step.
|
||||||
|
- UI-state capture pass (0.9.3 work): `src/test/capture_ui_states.gd` adds committed captures 14–21 for the start menu, pause, settings, shift review, emergency radio, action wheel, crowd panic and responder arrival. Every one of the 21 catalogue entries now has a current-build capture; `tools/check_screenshots.sh` reports 21 good / 0 bad and the automatic judge reports 21/21 match, 0 diff. Crowd panic and responder arrival are staged presentation frames, not gameplay changes.
|
||||||
|
- Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a tight additive flashlight spot with no beam geometry, and refreshed
|
||||||
|
staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts.
|
||||||
|
- Input/reliability fixes: selecting a venue releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. Mouse look now reads at `_input`, restores capture on window focus/click-back, and the HUD is mouse-transparent. Normal radio reports submit on click; emergency calls still require the hold-to-confirm path.
|
||||||
|
- Production-quality pass (v0.9.0): mixer/accessibility settings, audio volume clamping, 24-NPC budget smoke, shortened coat/dress stride, and a unified Godot 4.7.2 CI/local support baseline with documented upgrade/rollback requirements.
|
||||||
|
- Campaign and first-hour pass (v0.8.0): campaign progression, checkpoint resume, difficulty presets, a searchable Help overlay, recovery events, contextual dialogue, policy/debrief lines and tutorial/replay/skip. Scripted QA bypasses the tutorial unless `--tutorial` is passed.
|
||||||
|
- Tutorial pass (0.8.0 work): the eight-beat first shift is wired through a non-blocking prompt overlay, a safe compliant visitor, real wheel/inspection/warning/radio panels, persisted completion/skip and a Settings replay action. Scripted tests/captures bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers the full flow.
|
||||||
- Vision judge (v0.7.2): discovers the cheapest current OpenRouter image+JSON model, reports the model/fallbacks used, and clears the full screen-design judge (13/13 match, 0 diff).
|
- Vision judge (v0.7.2): discovers the cheapest current OpenRouter image+JSON model, reports the model/fallbacks used, and clears the full screen-design judge (13/13 match, 0 diff).
|
||||||
- Desktop HQ (v0.7.1): optional Forward+ Windows/Linux zip with volumetric fog, SDFGI and a volumetric flashlight; the default Compatibility/Web builds and headless tests gate the effect off. A/B captures live in `docs/screens/realism/2026-11-01-forward-plus/`.
|
- Desktop HQ (v0.7.1): optional Forward+ Windows/Linux zip with volumetric fog, SDFGI and a volumetric flashlight; the default Compatibility/Web builds and headless tests gate the effect off. A/B captures are generated locally under `docs/screens/realism/` (gitignored).
|
||||||
- Venue geometry (v0.7.0): the Joke Shop now builds as a narrow brick alley with a warm wall lantern, shutters, dumpster/AC clutter and no skyline; the TV Studio builds a stone facade, bulb canopy, glossy black car and barrier line. `WorldBuilder.VENUE_SPECS` drives facade/street/nav dimensions, and `GameManager` rebuilds only the world container when the venue changes.
|
- Hollywood landmark map (map optimization): the default venue is now a stylized reconstruction of 6840 Hollywood Blvd (the 1921 Masonic Temple / El Capitan Entertainment Centre) with a six-column Ionic portico, eight stone steps, Masonic parapet motto and medallions, three red event banners, central and side entrances, the El Capitan Theatre and east retail neighbours, the Ovation Hollywood arcade opposite, the Walk of Fame terrazzo and a four-lane boulevard with red kerb paint. The facade uses the shared limestone/stone material; the whole venue measures **58,884 triangles across 409 meshes** in the map-budget test. The venue menu exposes separate **Day** and **Night** Hollywood rows; the night row resolves to the same scenario with a late-phase offset, so its whole shift stays after dark. Real-site references and the geometry mapping are in [`hollywood-boulevard-map.md`](hollywood-boulevard-map.md).
|
||||||
|
- Day/night scenery variants (map optimization): the Hollywood row in the venue menu is split into Day and Night entries, with localized names/blurbs. The night entry uses `ShiftCoordinator.phase_offset_s` to start a full-length shift in `LATE` while the clock still starts at 00:00; the scheduler reads the offset time so late-night routine traffic and the late violent-threat event still fire, and the offset round-trips through checkpoints. Three `LATE` routine events keep the night street populated. Local lights were retuned from constant falloff to linear attenuation so sconces pool instead of flooding the stone.
|
||||||
|
- Joke Shop / Sunset Strip map (map optimization): the second venue is now a stylized reconstruction of The Comedy Store block at 8433 Sunset Blvd, with a 27 m black hall, signature-covered sloping canopy, circular **THE JOKE SHOP** blade sign, red velvet curtain bays, a west-end entrance, cream and white low-rise neighbours, a dry hillside with stucco houses and palms behind the roofline, and a low retail/billboard frontage across Sunset. The same geometry serves day and late lighting through the phase system. Real-site references and limits are in [`joke-shop-map.md`](joke-shop-map.md); the map measures **22,680 triangles across 454 meshes** in the budget test.
|
||||||
|
- Venue geometry (v0.7.0, Joke Shop frontage superseded by the map-optimization pass): the TV Studio builds a stone facade, bulb canopy, glossy black car and barrier line. `WorldBuilder.VENUE_SPECS` drives facade/street/nav dimensions, and `GameManager` rebuilds only the world container when the venue changes.
|
||||||
- Gear fit (post-v0.6.0): Blender gear is measured against the avatar head/skirt silhouette and the checks run in both the Python tooling and the headless suite. Sunglasses, the shoulder bag and the firefighter helmet were fitted to the new faces; the Influencer portrait and gallery were recaptured with a before/after report.
|
- Gear fit (post-v0.6.0): Blender gear is measured against the avatar head/skirt silhouette and the checks run in both the Python tooling and the headless suite. Sunglasses, the shoulder bag and the firefighter helmet were fitted to the new faces; the Influencer portrait and gallery were recaptured with a before/after report.
|
||||||
|
- End-to-end interaction pass (0.9.3 work): `smoke_ui_flows.gd` drives inspection allow/deny, normal vs emergency radio, Help filtering, wand allow/deny and review return-to-menu in the real scene; campaign and timeline smokes cover review continue and timeline round-trips.
|
||||||
|
- Destructive-action pass (0.9.3 work): reset campaign and tutorial replay share a guarded confirmation dialog (one open instance, one emission, Cancel is inert); emergency send remains hold-to-confirm with no post-submit double-fire. Covered by session/radio tests and recorded in `docs/ui-copy.md`.
|
||||||
|
- Font/layout pass (0.9.3 work): HUD font scale was exercised at 0.75× and 1.75×; the target chip measures and caps scaled names, top labels clip, and shared action buttons ellipsize long 1.5–2× labels instead of overflowing. Covered by accessibility/button tests and recorded in `docs/ui-copy.md`.
|
||||||
|
- Modal-close pass (0.9.3 work): one top-modal close path handles Esc/right-click for every overlay, Settings opened from the menu restores the menu, Help no longer opens Pause behind it, and pointer/guard freeze state is recaptured after each close. The session smoke covers these paths.
|
||||||
|
- Keyboard/gamepad focus pass (0.9.3 work): `UiFocus` rings wrap through visible/enabled controls, hidden Continue and disabled timeline actions are skipped, each modal has a default focus, and A/B are bound to `ui_accept`/`ui_cancel` for pad-only navigation. Recorded in `docs/ui-copy.md`.
|
||||||
|
- Button-state pass (0.9.3 work): a shared `UiStyles` helper now gives every action button explicit normal, hover, pressed, focus and disabled styles; the timeline disables Previous/Next at its boundaries, while hidden continue states and the keyboard-only tutorial skip remain intentional. Recorded in `docs/ui-copy.md`.
|
||||||
|
- Dialogue pass (0.9.3 work): NPC reason answers now use the authored `reason` context, with an in-character answer for every shared archetype; Hollywood and Studio gained door-staff lines; radio cancellation, tutorial Ask terminology, help, start hints and campaign reset copy were clarified. Audit recorded in `docs/ui-copy.md`.
|
||||||
|
- Pre-build NPC review (2026-11-02): all eight library characters were staged at portrait, conversation and crowd distance on Hollywood and the Joke Shop, in day and night lighting, with the normal crowd hidden (`src/test/capture_npc_review.gd`). No P0 issues were found. Two P1 gear-fit issues were fixed with runtime attach offsets: the authorized-guest lanyard badge now clears the jacket and the firefighter helmet sits above the eyes. Backpack straps, caps, hands and the intentionally low-poly faces were accepted for the stylized target; close-range flashlight wash remains an optional-lighting follow-up. The 24-NPC budget stayed at 6.94 ms, the gallery passed 13 good / 0 bad and the screen-design judge stayed 13/13, and a clean portrait scored a 0.732 vision gap against the photoreal concept.
|
||||||
|
|
||||||
Not yet implemented: a desktop-only Forward+ preset (the renderer stays GL Compatibility for Web parity), live 3D body-camera replay, editor-based GUT integration, and crowd performance profiling/LOD (see [`TODO.md`](../TODO.md)).
|
Current gaps for the 0.x release line: deeper authored content/dialogue, animation and audio production, localization/accessibility expansion, 24-NPC/platform profiling, external playtesting, and signed/notarized release packaging. The full gap analysis and milestone plan live in [`TODO.md`](../TODO.md); the current architecture and documentation ownership map are in [`docs/architecture.md`](architecture.md).
|
||||||
@@ -4,7 +4,7 @@ The complete design rationale also lives in [`PLAN.md`](../PLAN.md); this page k
|
|||||||
|
|
||||||
## Game Overview
|
## Game Overview
|
||||||
|
|
||||||
A typical shift lasts 10–15 minutes. The player:
|
A typical shift lasts 10–15 minutes. The first-person camera stays at the gameplay height of 1.7 m; the concept frames use a lower ~1.55 m reference camera, but gameplay readability and body-camera framing take priority. The player:
|
||||||
|
|
||||||
1. Watches the entrance, steps, queue, and surrounding sidewalk.
|
1. Watches the entrance, steps, queue, and surrounding sidewalk.
|
||||||
2. Determines why each person is approaching.
|
2. Determines why each person is approaching.
|
||||||
@@ -16,17 +16,26 @@ A typical shift lasts 10–15 minutes. The player:
|
|||||||
|
|
||||||
The game rewards calm resolutions rather than aggressive behavior. Emergency assistance and armed responders are rare last-resort systems with meaningful consequences.
|
The game rewards calm resolutions rather than aggressive behavior. Emergency assistance and armed responders are rare last-resort systems with meaningful consequences.
|
||||||
|
|
||||||
|
The first shift starts with an in-game tutorial covering movement/look, focus cycling, the action wheel, access inspection, polite direction, formal warning/body camera, radio and shift review. Its beat sheet is in [`tutorial.md`](tutorial.md). After that a short campaign runs Hollywood → Joke Shop → TV Studio; progress, difficulty and a pause-safe checkpoint are persisted locally. Help (from the venue menu) explains the controls and the current venue policy.
|
||||||
|
|
||||||
## Venues
|
## Venues
|
||||||
|
|
||||||
### Hollywood Boulevard Building
|
### El Capitan Entertainment Centre (6840 Hollywood Blvd)
|
||||||
|
|
||||||
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.
|
The map is a stylized reconstruction of the real building and block; the
|
||||||
|
reference notes and geometry mapping are in
|
||||||
|
[`hollywood-boulevard-map.md`](hollywood-boulevard-map.md). The venue menu
|
||||||
|
offers the same map as **Hollywood Boulevard — Day** and **Hollywood Boulevard
|
||||||
|
— Night**; the night variant starts directly in the late phase instead of
|
||||||
|
waiting for the shift clock.
|
||||||
|
|
||||||
|
The original scenario places the guard at the top of the eight stone steps outside the historic Masonic Temple / El Capitan Entertainment Centre on Hollywood Boulevard. 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.
|
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
|
||||||
|
|
||||||
The Joke Shop is a narrow brick alley with a recessed door, shuttered windows, a warm wall lantern, a dumpster and wall-mounted AC clutter. 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.
|
The Joke Shop is a Sunset Strip comedy club inspired by the real Comedy Store at 8433 Sunset Blvd: a black hall under a deep signature-covered canopy, a circular blade sign, red velvet curtains, a west-end entrance, neighbouring low-rise frontage and a dry hillside of houses behind. The real-site mapping is in [`joke-shop-map.md`](joke-shop-map.md). 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
|
### Hollywood Boulevard Late Night TV Studio
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Download the Godot 4.3+ editor (official build) and put `godot` on your PATH. CI pins 4.3; local development is verified with 4.7.2.
|
1. Download Godot **4.7.2** (official build) and put `godot` on your PATH. The project, local tooling and CI use the same version; engine upgrades require import, headless, screenshot and Desktop HQ smoke checks before changing the baseline.
|
||||||
2. Open or run the project from this directory.
|
2. Open or run the project from this directory.
|
||||||
|
|
||||||
## Run and Test
|
## Run and Test
|
||||||
@@ -20,6 +20,9 @@ godot --headless --path . --quit-after 120
|
|||||||
# Run all unit tests headlessly (exit code 0 = pass)
|
# Run all unit tests headlessly (exit code 0 = pass)
|
||||||
godot --headless --path . --script res://src/test/test_runner.gd
|
godot --headless --path . --script res://src/test/test_runner.gd
|
||||||
|
|
||||||
|
# Check docs README coverage, tracked images and local links
|
||||||
|
python3 tools/check_docs.py
|
||||||
|
|
||||||
# Save a screenshot to user:// (windowed)
|
# Save a screenshot to user:// (windowed)
|
||||||
godot --path . --script res://src/test/take_screenshot.gd
|
godot --path . --script res://src/test/take_screenshot.gd
|
||||||
|
|
||||||
@@ -32,13 +35,16 @@ godot --path . --script res://src/test/smoke_focus_radial.gd
|
|||||||
# Session-shell smoke: menu → shift → pause → settings → menu (headless or windowed)
|
# Session-shell smoke: menu → shift → pause → settings → menu (headless or windowed)
|
||||||
godot --headless --path . --script res://src/test/smoke_session_shell.gd
|
godot --headless --path . --script res://src/test/smoke_session_shell.gd
|
||||||
|
|
||||||
|
# End-to-end interaction smoke: inspection, radio, Help search, wand and review
|
||||||
|
godot --headless --path . --script res://src/test/smoke_ui_flows.gd
|
||||||
|
|
||||||
# PLAN-verb smoke: recognize, request reason, body camera, warning, blocking
|
# PLAN-verb smoke: recognize, request reason, body camera, warning, blocking
|
||||||
godot --headless --path . --script res://src/test/smoke_plan_verbs.gd
|
godot --headless --path . --script res://src/test/smoke_plan_verbs.gd
|
||||||
|
|
||||||
# Prop-collision smoke (windowed): guard stops at a bench, guest routes around it
|
# Prop-collision smoke (windowed): guard stops at a bench, guest routes around it
|
||||||
godot --path . --script res://src/test/smoke_props.gd
|
godot --path . --script res://src/test/smoke_props.gd
|
||||||
|
|
||||||
# NPC budget smoke: 16 full-rig avatars against the documented frame budget
|
# NPC budget smoke: 24 full-rig avatars against the documented frame budget
|
||||||
godot --headless --path . --script res://src/test/smoke_npc_budget.gd
|
godot --headless --path . --script res://src/test/smoke_npc_budget.gd
|
||||||
|
|
||||||
# Movement smoke (windowed): steps, precision walk, footsteps and landing
|
# Movement smoke (windowed): steps, precision walk, footsteps and landing
|
||||||
@@ -58,10 +64,42 @@ godot --headless --path . --script res://src/test/smoke_audio.gd
|
|||||||
|
|
||||||
# Scenario-content smoke: wand refusal, celebrity crowd spike
|
# Scenario-content smoke: wand refusal, celebrity crowd spike
|
||||||
godot --headless --path . --script res://src/test/smoke_scenario_content.gd
|
godot --headless --path . --script res://src/test/smoke_scenario_content.gd
|
||||||
|
|
||||||
|
# Tutorial smoke (windowed): completion, replay and skip; isolated user data
|
||||||
|
XDG_DATA_HOME=/tmp/gigline-tutorial godot --path . --fixed-fps 60 --script res://src/test/smoke_tutorial.gd -- --tutorial
|
||||||
|
|
||||||
|
# Campaign/checkpoint smoke (windowed): resume, review continue, help; isolated data
|
||||||
|
XDG_DATA_HOME=/tmp/gigline-campaign godot --path . --fixed-fps 60 --script res://src/test/smoke_campaign.gd
|
||||||
```
|
```
|
||||||
|
|
||||||
The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor.
|
The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor.
|
||||||
|
|
||||||
|
## Save Data and Migration
|
||||||
|
|
||||||
|
The game keeps four small JSON files in Godot's `user://` directory, which is
|
||||||
|
`~/.local/share/godot/app_userdata/Gig Line/` on Linux,
|
||||||
|
`%APPDATA%\Godot\app_userdata\Gig Line\` on Windows and
|
||||||
|
`~/Library/Application Support/Godot/app_userdata/Gig Line/` on macOS:
|
||||||
|
|
||||||
|
| File | Contents |
|
||||||
|
| --- | --- |
|
||||||
|
| `settings.json` | Camera, HUD, binding, difficulty, audio, accessibility and tutorial flags. Missing keys are filled from the current defaults; there is no schema version because the field set grew compatibly. |
|
||||||
|
| `campaign.json` | Campaign index, completed venues, difficulty, shift number and last grade. Older files with missing fields open at the safe defaults. |
|
||||||
|
| `shift_results.json` | Completed-shift review history, appended as an array. |
|
||||||
|
| `shift_state.json` | Resumable mid-shift checkpoint, version `1`. It stores the venue, guard/door/flashlight/body-camera state, coordinator snapshot and NPC rows. |
|
||||||
|
|
||||||
|
Writes go to a sibling `.tmp` file and are renamed only after the complete JSON
|
||||||
|
is written. A corrupt or partial file is ignored on load, so the player gets
|
||||||
|
defaults rather than a crash and a valid old file is not overwritten by a failed
|
||||||
|
replace. The migration fixtures used by the tests live in
|
||||||
|
`src/test/fixtures/saves/`.
|
||||||
|
|
||||||
|
For a clean first launch, delete the four files (or the whole `Gig Line` user
|
||||||
|
directory). Settings' **Reset campaign** clears campaign progress separately.
|
||||||
|
To back up or move a profile, copy those files while the game is closed; to roll
|
||||||
|
back, restore the previous copies. Shift history is diagnostic and is not
|
||||||
|
required to resume a checkpoint.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
Keyboard and mouse are the default; a gamepad works without configuration (left stick moves,
|
Keyboard and mouse are the default; a gamepad works without configuration (left stick moves,
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# Hollywood Boulevard map reference
|
||||||
|
|
||||||
|
The Hollywood venue is a stylized reconstruction of the real block around the
|
||||||
|
El Capitan Entertainment Centre. It is a game space, not a survey-accurate
|
||||||
|
model: the landmark silhouette, street wall and approach read from the player's
|
||||||
|
eye height, while side streets and interiors are simplified.
|
||||||
|
|
||||||
|
## Real site
|
||||||
|
|
||||||
|
- **Address:** 6840 Hollywood Boulevard, Hollywood, Los Angeles, CA 90028.
|
||||||
|
- **Coordinates:** 34.10118 N, 118.34009 W (OSM way `424024147`).
|
||||||
|
- **Building:** the 1921 Hollywood Masonic Temple, designed by John C. Austin in
|
||||||
|
the Classical Revival style; now the El Capitan Entertainment Centre and the
|
||||||
|
home of *Jimmy Kimmel Live!*.
|
||||||
|
- **Facade:** a light stone/render Neoclassical front with six oversized Ionic
|
||||||
|
columns, a recessed main entrance reached by eight stone steps, ornamental
|
||||||
|
iron door grilles, a continuous crosscut-grille transom, two street-level side
|
||||||
|
entrances, and a parapet with the Masonic motto and circular medallions.
|
||||||
|
- **Neighbours:** the El Capitan Theatre (6838) immediately west, a modern
|
||||||
|
retail block east, and the Ovation Hollywood frontage (6801) across the
|
||||||
|
four-lane boulevard.
|
||||||
|
- **Sidewalk:** the Hollywood Walk of Fame terrazzo strip with brass stars runs
|
||||||
|
along the kerb; the kerb is painted red in the reference photographs.
|
||||||
|
|
||||||
|
## Game mapping
|
||||||
|
|
||||||
|
| Real feature | In-game representation |
|
||||||
|
| --- | --- |
|
||||||
|
| 29.6 m wide, 14.5 m high facade | `VENUE_SPECS["hollywood_building"]`: `facade_width` 29.6, `facade_height` 14.5, front at `z = -4` |
|
||||||
|
| Eight stone steps | `stair_count` 8, `stair_height` 0.10, `stair_tread` 0.38, 12 m wide |
|
||||||
|
| Six Ionic columns | `IonicColonnade` at x = -12.4 … 12.4, 4.96 m bay spacing |
|
||||||
|
| Recessed main entrance + two side doors | central 4.2 m door and 2.5 m side doors with transoms |
|
||||||
|
| Red event banners | three stylized `EL / CAPITAN / LIVE` banners in the central bays |
|
||||||
|
| Masonic motto + medallions | `FriezeInscription` label and two stone discs |
|
||||||
|
| El Capitan Theatre | `NeighbourBlocks/ElCapitanTheatre` with marquee, blade sign and practical light |
|
||||||
|
| Retail block east | `NeighbourBlocks/EastRetailBlock` with glazed shopfront |
|
||||||
|
| Ovation Hollywood | `OvationHollywood` arcade with seven arched openings, sign and south sidewalk |
|
||||||
|
| Walk of Fame | `WalkOfFame` dark terrazzo band with ten stars |
|
||||||
|
| Four-lane boulevard | double-yellow centre, broken white lane lines, red kerb paint and crosswalk |
|
||||||
|
|
||||||
|
The generated AI references live in
|
||||||
|
[`docs/screens/concepts/hollywood_boulevard/`](screens/concepts/hollywood_boulevard/README.md).
|
||||||
|
They are art direction for lighting, material scale and composition; they are
|
||||||
|
not texture or geometry sources.
|
||||||
|
|
||||||
|
## Deliberate simplifications
|
||||||
|
|
||||||
|
- Show branding is replaced by the venue-neutral `EL / CAPITAN / LIVE` banner
|
||||||
|
text. The building name and address are retained.
|
||||||
|
- The opposite Ovation frontage is represented as a single arcade block rather
|
||||||
|
than its full mall plan. Interiors, the Metro entrance and side streets are
|
||||||
|
out of playable bounds.
|
||||||
|
- The AI concept frames are 1280×720 references. The in-game geometry is built
|
||||||
|
from boxes, cylinders and a small number of custom arch/star meshes so it
|
||||||
|
stays inside the Compatibility/Web budget.
|
||||||
|
- The venue menu offers Day and Night Hollywood rows. Night is the same
|
||||||
|
geometry and scenario with a late-phase offset, not a second baked map; the
|
||||||
|
phase still drives sun, sky, fog, windows, signage and practical lights.
|
||||||
|
- The landmark facade uses the shared smooth limestone/stone texture rather
|
||||||
|
than the noisier granite panel tile. The whole venue measures **58,884
|
||||||
|
triangles across 409 meshes** in `test_hollywood_geometry_stays_inside_the_map_budget`,
|
||||||
|
well inside the 150k/600 budget.
|
||||||
|
- Local lights use linear attenuation (`omni_attenuation = 1.0`). Godot treats
|
||||||
|
`0.0` as constant falloff, which previously turned the night facade into a
|
||||||
|
flat yellow wall; the sconces now pool light instead.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Hollywood Masonic Temple — Wikipedia](https://en.wikipedia.org/wiki/Hollywood_Masonic_Temple)
|
||||||
|
- [El Capitan Entertainment Centre — OpenStreetMap way 424024147](https://www.openstreetmap.org/way/424024147)
|
||||||
|
- [El Capitan Theatre — OpenStreetMap way 424024148](https://www.openstreetmap.org/way/424024148)
|
||||||
|
- [Ovation Hollywood — OpenStreetMap way 151485232](https://www.openstreetmap.org/way/151485232)
|
||||||
|
- [Hollywood Masonic Temple facade — Wikimedia Commons](https://commons.wikimedia.org/wiki/File:El_Capitan_Theater_frontal_fachade-The_Masonic_Temple.jpg)
|
||||||
|
- [Hollywood Masonic Temple and El Capitan Theatre, 2013 — Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Hollywood_Masonic_Temple_2013-10-13_10-02-25.jpg)
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
godot --headless --path . --script res://src/test/test_runner.gd
|
||||||
|
godot --path . --script res://src/test/capture_realism.gd -- --out=/tmp/gig-line-hollywood --stages=day,evening,late
|
||||||
|
python3 tools/check_docs.py
|
||||||
|
```
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# The Joke Shop — Sunset Strip map notes
|
||||||
|
|
||||||
|
The game's second venue is a fictional comedy club called **The Joke Shop**. Its
|
||||||
|
block, canopy, blade sign, entrance and hillside are based on the real Comedy
|
||||||
|
Store at **8433 Sunset Blvd, West Hollywood, CA 90069**.
|
||||||
|
|
||||||
|
The club name and the game's guest stories are invented. The building is a
|
||||||
|
stylized reconstruction, not a survey-accurate model: the playable frontage is
|
||||||
|
27 m, while the real parcel is much deeper and contains several rooms that the
|
||||||
|
game does not enter.
|
||||||
|
|
||||||
|
## Real site
|
||||||
|
|
||||||
|
| Item | Reference |
|
||||||
|
| --- | --- |
|
||||||
|
| Address | 8433 Sunset Blvd, West Hollywood, CA 90069, United States |
|
||||||
|
| Real venue | The Comedy Store (comedy club and theatre) |
|
||||||
|
| OpenStreetMap | Way [`428854155`](https://www.openstreetmap.org/way/428854155), centroid 34.095265, -118.373965 |
|
||||||
|
| OSM tags | `amenity=theatre`, `building=commercial`, `height=12.2`, `start_date=1940`, `wikidata=Q3520350` |
|
||||||
|
| Footprint | OSM polygon bounding box about **47.1 m × 41.2 m** across the club and rear parcels |
|
||||||
|
| Street | Sunset Boulevard, a major east–west arterial on the Sunset Strip |
|
||||||
|
| Setting | Dry Hollywood Hills directly behind the club, with houses and palms rising above the roofline |
|
||||||
|
|
||||||
|
The building began as **Ciro's**, a 1940 nightclub; it became The Comedy Store
|
||||||
|
in the early 1970s. Its street front is a low black hall under a deep,
|
||||||
|
signature-covered canopy, with the circular blade sign above the roof, red
|
||||||
|
curtains along the frontage and the main entrance set toward the west end. The
|
||||||
|
hillside houses behind it are part of the silhouette from the sidewalk.
|
||||||
|
|
||||||
|
## How the map maps to the real site
|
||||||
|
|
||||||
|
| Real element | Game implementation |
|
||||||
|
| --- | --- |
|
||||||
|
| Low black comedy hall | 27 m × 12 m footprint, 8.4 m street wall; matte black concrete surface with a low plinth and parapet |
|
||||||
|
| Deep overhanging canopy | 4.9 m sloped slab dropping about 1 m to the outer fascia; the fascia carries white signature lettering and the club name |
|
||||||
|
| Signature-covered front | Two `Label3D` signature bands on the fascia plus a smaller wall panel, so the handwriting reads at gameplay distance without dozens of decals |
|
||||||
|
| Circular blade sign | 3.9 m white disc with a black rim on a roof pole; `THE JOKE SHOP` lettering faces the street |
|
||||||
|
| Red curtains | Four three-fold velvet bays with tiebacks between the entrance and the east end; a red velvet entrance curtain |
|
||||||
|
| West-end entrance | Recessed opening at x = -8.8 m with a low step, brass pulls and the door-lock indicator |
|
||||||
|
| Warm practicals | Sconces along the wall, canopy downlights, entrance wash and three ground uplights; all off in daylight and switched by `set_night_mode()` |
|
||||||
|
| Neighbouring low-rise frontage | Cream Mediterranean block west (balconies, red awning) and white commercial block east (ribbon windows, lobby) |
|
||||||
|
| Hillside and houses | Fog-free unshaded 60° ramp behind the rear wall with stucco houses, shrubs and palms; its tint drops to a dark silhouette at night |
|
||||||
|
| Opposite side of Sunset | Low retail row, rooftop billboard, neon and palms as background context beyond the far kerb |
|
||||||
|
| Sidewalk furniture | Palms, queue barriers, stanchions, bins, planters, a-frame sign, hydrant and a parked sedan; prop placements pass the shared validator |
|
||||||
|
|
||||||
|
## Scene composition
|
||||||
|
|
||||||
|
- **Frontage:** 27 m wide, façade centre at z = -8.0, front face at z = -2.0.
|
||||||
|
- **Sidewalk:** 38 m wide × 14 m deep, meeting the kerb at z = 11.9.
|
||||||
|
- **Street:** 90 m × 24 m, with the four-lane markings reused from the
|
||||||
|
Hollywood block and a crosswalk in front of the entrance.
|
||||||
|
- **Playable area:** x = -15…15, z = -0.6…14.0. The guard starts on the west
|
||||||
|
side of the entrance at (-4.8, 0.1, 1.4), facing the approach.
|
||||||
|
- **Map budget:** the headless audit measures **22,680 triangles across 454
|
||||||
|
meshes**, well inside the 150k/700 test budget.
|
||||||
|
|
||||||
|
The same geometry serves the day and late-night looks. There is no second baked
|
||||||
|
map: the phase lighting, skyline/fog tint, lit windows and practicals change,
|
||||||
|
just as with the Hollywood day/night rows.
|
||||||
|
|
||||||
|
## Simplifications and limits
|
||||||
|
|
||||||
|
- The real club's rear rooms, basement and roof plant are not modelled; the
|
||||||
|
playable area is the street frontage only.
|
||||||
|
- The 47 m OSM footprint includes rear parcels and does not match the 27 m
|
||||||
|
street frontage; the game keeps the frontage proportions rather than the full
|
||||||
|
lot.
|
||||||
|
- Neighbouring businesses are represented by generic low-rise blocks. Their
|
||||||
|
exact names and current tenants were not verified, so the docs do not claim
|
||||||
|
them.
|
||||||
|
- The hillside is a background ramp with simplified houses, not a terrain
|
||||||
|
heightfield; it exists to restore the real silhouette above the roofline.
|
||||||
|
- Signage uses the fictional **The Joke Shop** name, not the real Comedy Store
|
||||||
|
wordmark.
|
||||||
|
- The concept frames are photoreal; the game keeps its stylized, low-poly
|
||||||
|
material treatment. The vision judge therefore still scores material and
|
||||||
|
foliage detail below the reference. The remaining texture/realism work is
|
||||||
|
tracked with the postponed 0.9.5 pass, not treated as a map-geometry defect.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- OpenStreetMap way 428854155: <https://www.openstreetmap.org/way/428854155>
|
||||||
|
- Wikimedia Commons photographs used for proportion and material study:
|
||||||
|
- <https://commons.wikimedia.org/wiki/File:ComedyStore_01.jpg>
|
||||||
|
- <https://commons.wikimedia.org/wiki/File:ComedyStore_02.jpg>
|
||||||
|
- <https://commons.wikimedia.org/wiki/File:The_Comedy_Store.jpg>
|
||||||
|
- <https://commons.wikimedia.org/wiki/File:The_Comedy_Store_West_Hollywood_(51017780551).jpg>
|
||||||
|
- AI art-direction frames: [`screens/concepts/joke_shop/`](screens/concepts/joke_shop/README.md)
|
||||||
|
- Verification: `godot --headless --path . --script res://src/test/test_runner.gd`
|
||||||
|
(`test_joke_shop_uses_the_real_sunset_strip_layout`,
|
||||||
|
`test_joke_shop_geometry_stays_inside_the_map_budget`), and
|
||||||
|
`godot --path . --script res://src/test/capture_realism.gd -- --stages=joke_shop_day,joke_shop`.
|
||||||
|
The committed gallery passed `tools/check_screenshots.sh` (13 good / 0 bad)
|
||||||
|
and the screen-design judge (13/13 match, 0 diff). The latest local realism
|
||||||
|
run reports a reference gap of 0.454 for the night entrance.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Known Limitations
|
||||||
|
|
||||||
|
This page records the limitations that remain at the 0.10.0 release-candidate
|
||||||
|
stage. Items marked **blocker** must be cleared or explicitly accepted before a
|
||||||
|
public release.
|
||||||
|
|
||||||
|
## Release blockers
|
||||||
|
|
||||||
|
- **External playtest:** at least five first-time-player sessions and the
|
||||||
|
resulting onboarding/accessibility triage have not been completed. The
|
||||||
|
tutorial/campaign evidence is currently automated smoke coverage, not player
|
||||||
|
observation.
|
||||||
|
- **AI asset provenance:** the image-generation provider terms and commercial
|
||||||
|
redistribution rights for the committed AI-assisted textures/UI art still need
|
||||||
|
legal review. See [`../CREDITS.md`](../CREDITS.md).
|
||||||
|
- **Storefront/legal approval:** age/content rating, storefront policy, trademark
|
||||||
|
language and the mental-health-crisis/body-camera representation have not been
|
||||||
|
externally approved.
|
||||||
|
- **macOS signing/notarization:** the 0.10.0 release candidate ships unsigned
|
||||||
|
with right-click/`xattr` quarantine instructions. Signing and notarization are
|
||||||
|
required before a broad public/storefront release and remain blocked until an
|
||||||
|
Apple Developer account and signing job exist.
|
||||||
|
- **Hardware matrix:** Web, Windows and Desktop HQ have been built and checked
|
||||||
|
structurally; a complete target-hardware performance/launch matrix is still
|
||||||
|
pending.
|
||||||
|
|
||||||
|
## Content and presentation
|
||||||
|
|
||||||
|
- The venues are stylized reconstructions, not survey-accurate models. The Joke
|
||||||
|
Shop is a fictional stand-in for the real Sunset Strip block.
|
||||||
|
- Character faces, hands and fabric use a deliberately low-poly style; the
|
||||||
|
>26 m tier remains a two-quad impostor and there is no separate finger-curl
|
||||||
|
rig. The accepted limits are recorded in
|
||||||
|
[`npc-visual-review.md`](npc-visual-review.md).
|
||||||
|
- There is no live 3D body-camera replay; the timeline is text-based.
|
||||||
|
- There is no recorded voice/music track. Ambience and cues are procedural.
|
||||||
|
- The game is English-only and does not yet have a translator workflow or
|
||||||
|
pseudo-locale layout sign-off.
|
||||||
|
|
||||||
|
## Persistence and platform
|
||||||
|
|
||||||
|
- Save migration is covered by synthetic 0.8/0.9 fixtures and failure-mode tests,
|
||||||
|
not by a real player profile collected from every earlier release.
|
||||||
|
- Desktop HQ requires a Vulkan-capable GPU/driver; the default Compatibility and
|
||||||
|
Web builds remain the broad target.
|
||||||
|
- The manual smoke checklist is completed by developers; external device
|
||||||
|
coverage (Windows installer, macOS launch, gamepad and Web hosts) is not yet a
|
||||||
|
complete evidence set.
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
# NPC model sourcing and expansion plan
|
||||||
|
|
||||||
|
Status: **direction chosen 2026-11-03.** The procedural generator is the primary
|
||||||
|
path, the Quaternius CC0 pack is a reviewed reference/prototype, and MakeHuman is
|
||||||
|
deferred to a separate local development pipeline. No downloaded source model is
|
||||||
|
committed to the game or included in a release; the committed research preview
|
||||||
|
sheets carry the required attribution. Local evaluation files live under
|
||||||
|
`tmp/npc-research/` (gitignored). The decision is recorded in
|
||||||
|
[ADR-0015](adr/0015-npc-model-variety-sourcing.md).
|
||||||
|
|
||||||
|
This page records the free/open-source character sources that were reviewed, how
|
||||||
|
they behave when imported into the existing Blender tooling, and the options for
|
||||||
|
creating a larger NPC model library. The runtime contract and current library are
|
||||||
|
documented in [`blender-assets.md`](blender-assets.md) and
|
||||||
|
[`npc-visual-review.md`](npc-visual-review.md).
|
||||||
|
|
||||||
|
## Why this review exists
|
||||||
|
|
||||||
|
The library has eight skinned characters, and `NpcAvatar.character_model_for()`
|
||||||
|
reuses them across role families. That is enough for a release candidate, but
|
||||||
|
crowds repeat the same faces, builds and outfits. Three venues, a 24-NPC budget
|
||||||
|
and close-up portraits make that repetition visible.
|
||||||
|
|
||||||
|
Any expansion must preserve the existing integration contract:
|
||||||
|
|
||||||
|
- mesh node `npc_base` under a `Skeleton3D`;
|
||||||
|
- bones `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R` plus
|
||||||
|
`Hips`, `Spine`, `Neck` at the contract rest positions;
|
||||||
|
- five tint slots (`skin`, `cloth`, `pants`, `accessory`, `hair`);
|
||||||
|
- front on Blender −Y / Godot +Z, feet near the floor, 1.6–2.1 m height;
|
||||||
|
- at most 8,000 triangles and 8,000 glTF vertices per character;
|
||||||
|
- gear attach points at head, back, chest and hand.
|
||||||
|
|
||||||
|
The existing `audit` command is the gate. A source that looks good in a preview
|
||||||
|
but fails `audit` is not usable without an adaptation step.
|
||||||
|
|
||||||
|
## What was evaluated
|
||||||
|
|
||||||
|
Blender 5.2.0 LTS (Flatpak) imported each file in background mode. Measurements
|
||||||
|
below come from the imported scene, because external GLBs often carry node
|
||||||
|
transforms that the project's audit (which reads accessor-local bounds) does not
|
||||||
|
apply. "Vertices" are the imported, seam-split counts that matter for the glTF
|
||||||
|
budget, not the source file's vertex count.
|
||||||
|
|
||||||
|
| Source | Licence | Files checked | Geometry / rig | Height | Verdict |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| **Quaternius Ultimate Modular Men / Women** (mirrored GLB pack) | CC0 1.0 | `Casual_Hoodie.glb`, `Worker.glb`, `Casual.glb` | 5,240–6,424 tris; 10,534–12,968 verts; 62 bones; 24 animations; 4 skinned meshes on one armature | 1.80–1.87 m | **Best silhouette/outfit match.** Fails the contract: wrong bone names/rest positions, no tint slots, no `npc_base` node, vertex budget exceeded. |
|
||||||
|
| **Kenney Blocky Characters 2.0** | CC0 1.0 | full pack (18 characters), `character-a.glb`, `character-f.glb` | 72 tris; 143 verts; static, no armature; one texture atlas per character | 2.7 m imported (needs 0.67× scale) | **Not a close-up character.** Useful as a crowd/impostor LOD or placeholder, but cannot be driven by the skeleton and has no separate face/clothing slots. |
|
||||||
|
| **Khronos CesiumMan** | CC BY 4.0 (© Cesium) | `CesiumMan.glb` | 4,672 tris; 3,273 verts; 19-bone walk rig; 1 material/texture; 1 animation | 1.45 m | **Technical retarget test only.** One outfit and one look, wrong rig, short; works as a skinning/pose reference. Attribution is required. |
|
||||||
|
| **Khronos RiggedFigure** | CC BY 4.0 (© Cesium) | `RiggedFigure.glb` | 256 tris; 370 verts; 19-bone walk rig; no UVs; 1 animation | 1.48 m | **Rig smoke test only.** A mannequin with no usable surface detail. |
|
||||||
|
| **MakeHuman core base mesh / MPFB** | Core assets CC0 1.0; application AGPL/GPL | `makehuman/data/3dobjs/base.obj` | 19,158 verts; 18,486 polygons; 36,972 tris; no rig in the base mesh | ~16.9 model units before scaling | **Best body-shape diversity, highest pipeline cost.** Needs scaling, decimation/retopology and a generated rig. The MakeHuman/MPFB tool itself is not bundled. |
|
||||||
|
|
||||||
|
Local review sheet (front views, not committed):
|
||||||
|
`tmp/npc-research/previews/contact_sheet.jpg`.
|
||||||
|
|
||||||
|
Rejected without downloading: Mixamo and Ready Player Me. Both are useful
|
||||||
|
services, but their terms are not a simple free/open-source redistribution basis
|
||||||
|
for shipped character meshes. Sketchfab, Poly Pizza and OpenGameArt can contain
|
||||||
|
excellent CC0 work, but each asset needs a case-by-case licence and provenance
|
||||||
|
check; they are not a consistent library source.
|
||||||
|
|
||||||
|
### Measured contract failures
|
||||||
|
|
||||||
|
Every example fails the unmodified `audit` contract:
|
||||||
|
|
||||||
|
| Check | Quaternius | Kenney | CesiumMan | RiggedFigure | MakeHuman base |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| `npc_base` mesh node | no | no | no | no | no |
|
||||||
|
| Required 13 bones | no (62 different names) | no rig | no (19 different names) | no (19 different names) | no rig |
|
||||||
|
| Five tint slots | no | no | no | no | no |
|
||||||
|
| ≤ 8,000 tris | yes | yes | yes | yes | no |
|
||||||
|
| ≤ 8,000 verts | no (10.5–13k) | yes | yes | yes | no (19k) |
|
||||||
|
| Feet on floor | yes | yes (2.7 m scale) | yes (−11 mm) | yes | no (needs scaling) |
|
||||||
|
| Height 1.6–2.1 m | yes | no (2.7 m) | no (1.45 m) | no (1.48 m) | needs scale |
|
||||||
|
| Skinned / UV mapped | yes / yes | no / yes | yes / yes | yes / no | no / no |
|
||||||
|
|
||||||
|
The practical conclusion is that **there is no drop-in download**. External
|
||||||
|
sources need a deliberate intake step, and the expensive part is not decimation:
|
||||||
|
it is rebuilding or retargeting the skeleton and material slots without breaking
|
||||||
|
the animation proxies and gear placement.
|
||||||
|
|
||||||
|
The table uses Blender-imported world bounds. The stdlib `audit` reads accessor-local
|
||||||
|
bounds and ignores node transforms, so an unapplied external GLB can fail floor/height
|
||||||
|
checks even when the imported scene looks correct. The intake step must apply
|
||||||
|
transforms before it measures and exports.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### Option A — extend the procedural generator
|
||||||
|
|
||||||
|
Add more variation to `tools/blender_assets.py` rather than importing models:
|
||||||
|
body builds, height ranges, head/face shapes, hair and facial hair, outfit
|
||||||
|
layers, and more `CHARACTERS` specs. This keeps a single owned style, needs no
|
||||||
|
third-party licence review, and stays inside the existing builder/audit flow.
|
||||||
|
|
||||||
|
- **Strengths:** lowest integration risk, deterministic, no new dependencies.
|
||||||
|
- **Limits:** the current face and clothing vocabulary is intentionally simple;
|
||||||
|
more variants may still read as the same model with a different coat.
|
||||||
|
- **Best for:** a quick 4–8 variant expansion and filling obvious role gaps.
|
||||||
|
|
||||||
|
### Option B — Quaternius CC0 intake with a contract adapter
|
||||||
|
|
||||||
|
Use Quaternius meshes as silhouette/outfit donors and adapt them to the project
|
||||||
|
contract. The visible result is a different body/clothing language from the
|
||||||
|
current library, so the adapter must also normalise head/hand style or rebuild
|
||||||
|
those parts.
|
||||||
|
|
||||||
|
- **Strengths:** best free outfit/hair variety, animations included, CC0,
|
||||||
|
already close to game proportions, front axis and floor contact.
|
||||||
|
- **Work:** weld/decimate to the vertex budget, remap materials to the five
|
||||||
|
slots, rebuild the armature at the contract rest positions, transfer weights,
|
||||||
|
repose/normalise proportions, rename the mesh node, export, audit and compare
|
||||||
|
in the acceptance harness.
|
||||||
|
- **Main risk:** retargeting a 62-bone T-pose rig to the 13-bone arm-down
|
||||||
|
contract. Hip/knee rest heights differ substantially (Quaternius upper leg
|
||||||
|
starts near 0.96 m; the contract hip starts near 0.62 m), so a naive
|
||||||
|
nearest-bone weight transfer can produce a good still image and bad walk
|
||||||
|
poses. Prototype one character before committing to a roster.
|
||||||
|
- **Best for:** a larger, visibly different library if the prototype passes the
|
||||||
|
portrait and walk gates.
|
||||||
|
|
||||||
|
### Option C — MakeHuman/MPFB body-shape pipeline
|
||||||
|
|
||||||
|
Generate a range of ages, builds and body shapes from the CC0 MakeHuman base
|
||||||
|
mesh, then replace the head/hands and dress them with project-owned clothing.
|
||||||
|
This is the strongest answer to "everyone has the same body", but requires an
|
||||||
|
offline authoring step, decimation/retopology and a custom rig/weight step. It
|
||||||
|
should be treated as a later pipeline project, not a quick content drop.
|
||||||
|
|
||||||
|
## Decision and prototype outcome
|
||||||
|
|
||||||
|
The project owner reviewed this page on 2026-11-03. The decision is:
|
||||||
|
|
||||||
|
1. **CC0 is acceptable** for shipped character assets. CC-BY is acceptable only
|
||||||
|
with attribution in `CREDITS.md`; service assets whose terms restrict
|
||||||
|
redistribution remain excluded.
|
||||||
|
2. **Extend the procedural generator** as the primary path, starting with
|
||||||
|
crowd-variety looks.
|
||||||
|
3. **Prototype Quaternius** as a secondary source. It is not approved for
|
||||||
|
shipping yet; the contract-retarget result below is a no-go for the current
|
||||||
|
skeleton.
|
||||||
|
4. **Use crowd variety** as the first visual goal, not role-specific costumes.
|
||||||
|
5. **Evaluate MakeHuman for a later, separate local development pipeline.** It
|
||||||
|
is not part of the normal build, CI or release flow.
|
||||||
|
6. **Commit the research previews with attribution.** The CC-BY entries are
|
||||||
|
listed in `CREDITS.md` and remain development-only.
|
||||||
|
|
||||||
|
### Quaternius prototype
|
||||||
|
|
||||||
|
The prototype used `Casual_Hoodie.glb`:
|
||||||
|
|
||||||
|
- **Geometry budget is not the blocker.** Imported: 12,518 vertices / 6,206
|
||||||
|
triangles. After welding seam splits: 3,199 vertices / 6,206 triangles. After
|
||||||
|
a 0.75 decimate: 2,423 vertices / 4,654 triangles, comfortably inside the
|
||||||
|
library budget.
|
||||||
|
- **Animation quality is attractive.** The imported Idle and Walk clips read
|
||||||
|
well at game distance, and the model brings 24 animations.
|
||||||
|
- **The contract rest pose is the blocker.** The source is a T-pose 62-bone rig
|
||||||
|
with realistic proportions: upper leg starts near 0.96 m, arm span is about
|
||||||
|
1.68 m, and the shoulder joint sits near x = 0.153 m. The project contract is
|
||||||
|
an arm-down 13-bone rig with a hip near 0.62 m, a shoulder near x = 0.27 m and
|
||||||
|
a hand near z = 0.78 m. Rebuilding the contract armature around this mesh would
|
||||||
|
either distort the body substantially or require a full proportion warp.
|
||||||
|
- **Decision:** do not retarget Quaternius to the contract now. Keep it as a
|
||||||
|
silhouette/style reference and, if a second rig path is ever wanted, prototype
|
||||||
|
it as its own animation path (its own skeleton, AnimationPlayer clips and
|
||||||
|
per-model gear offsets) rather than forcing it into `npc_base`. That is a
|
||||||
|
separate architecture decision, not an asset conversion.
|
||||||
|
|
||||||
|
Preview: [`blender_previews/sheet_npc_sources_research.jpg`](blender_previews/sheet_npc_sources_research.jpg).
|
||||||
|
|
||||||
|
### Generator crowd prototype
|
||||||
|
|
||||||
|
Four proposed crowd specs were built with the existing generator in a temporary
|
||||||
|
output directory. All four audit clean and stay inside the character budget:
|
||||||
|
|
||||||
|
| Prototype | Look | Tris / verts | Gear |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `npc_crowd_a` | slim, bun, sunglasses | 4,644 / 4,356 | sunglasses |
|
||||||
|
| `npc_crowd_b` | broad, bald, beard | 4,550 / 4,453 | shoulder bag |
|
||||||
|
| `npc_crowd_c` | average, short hair, long coat | 5,118 / 5,086 | umbrella |
|
||||||
|
| `npc_crowd_d` | slim, short hair, hood up | 5,258 / 4,907 | cup |
|
||||||
|
|
||||||
|
They vary build, head silhouette, hair/beard and outer layer while keeping the
|
||||||
|
existing face language. They are **not in the shipped catalogue**; production
|
||||||
|
integration belongs to the roster/runtime work under the unversioned `Character
|
||||||
|
development` section, after the current release candidate.
|
||||||
|
|
||||||
|
Preview: [`blender_previews/sheet_npc_crowd_prototypes.jpg`](blender_previews/sheet_npc_crowd_prototypes.jpg).
|
||||||
|
|
||||||
|
### MakeHuman local-pipeline evaluation
|
||||||
|
|
||||||
|
The CC0 base mesh is 19,158 vertices / 18,486 polygons (36,972 triangles after
|
||||||
|
triangulation), with 21,334 UV coordinates but no normals, material groups or
|
||||||
|
rig. Scaling it to 1.82 m and decimating to 0.35 produced 3,780 vertices / 6,468
|
||||||
|
triangles, which fits the geometry budget but is a collapse-decimation result,
|
||||||
|
not production topology.
|
||||||
|
|
||||||
|
MakeHuman is therefore **feasible only as a separate local body-shape pipeline**.
|
||||||
|
The boundary for that future work is:
|
||||||
|
|
||||||
|
- MPFB/MakeHuman is installed and run by a contributor, never by CI or the game
|
||||||
|
build.
|
||||||
|
- Generated bodies and intermediate exports stay in `tmp/` (gitignored); the
|
||||||
|
CC0 base mesh is not committed.
|
||||||
|
- The contributor scales to 1.8–1.9 m, decimates or retopologises to roughly
|
||||||
|
6–7k triangles, and checks the silhouette against `npc-visual-review.md`.
|
||||||
|
- The body gets the contract armature, weights, project-owned head/hands,
|
||||||
|
clothing and gear; it must pass `blender_assets.py audit` and `fit`.
|
||||||
|
- Only the final CC0-derived GLB and its provenance note are committed. The
|
||||||
|
application code and any non-CC0 assets stay outside the repository.
|
||||||
|
|
||||||
|
This is deferred; it is not part of the current crowd-variety work.
|
||||||
|
|
||||||
|
## Provisional NPC roster
|
||||||
|
|
||||||
|
The design rule stays: clothes, props and behaviour carry the read; a model must
|
||||||
|
not label a person as "the threat". The roster therefore adds ordinary adults
|
||||||
|
across age, build and style, not costumes that telegraph the outcome.
|
||||||
|
|
||||||
|
| Look family | Current | Proposed additions | Variation axes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Everyday | `npc_casual`, `npc_jacket`, `npc_coat`, `npc_hoodie`, `npc_dress` | `npc_crowd_a` (bun/slim), `npc_crowd_b` (bald/broad), `npc_crowd_c` (coat/umbrella), `npc_crowd_d` (hood/cup) | build, height, hair, facial hair, outer layer, accessory |
|
||||||
|
| Workwear | `npc_vest`, `npc_guard` | later: second hi-vis build, venue-security build | build, height, headwear, badge/radio gear |
|
||||||
|
| Responder | `npc_firefighter` | later: paramedic/medical responder only if the design supports it | turnout vs medical kit, build, helmet |
|
||||||
|
| Crowd/LOD | none | optional Kenney blocky or a decimated library variant for distant crowds | silhouette and palette only |
|
||||||
|
|
||||||
|
A first target is **12 library characters** (the current eight plus the four
|
||||||
|
crowd prototypes), not twenty. That is enough to break the clone effect without
|
||||||
|
doubling the portrait QA matrix. `character_model_for()` becomes a pool per role
|
||||||
|
family with a deterministic seed choice; the current two-way alternation for
|
||||||
|
guests, trespassers and threats is the template.
|
||||||
|
|
||||||
|
## Implementation plan
|
||||||
|
|
||||||
|
The actionable tasks live in `TODO.md` under the `Character development` section. The workstreams
|
||||||
|
are:
|
||||||
|
|
||||||
|
1. **Decision and licence gate.** Complete: ADR-0015, the CC0/CC-BY rule and the
|
||||||
|
committed preview sheets.
|
||||||
|
2. **Generator crowd variants.** Complete as a prototype (four specs, clean
|
||||||
|
audit, review sheet). Production integration is pending the current release candidate.
|
||||||
|
3. **Quaternius intake.** The contract-retarget prototype is a no-go. Keep the
|
||||||
|
source as a reference; a second-rig/animation path would need its own ADR.
|
||||||
|
4. **Roster build and runtime.** Add the four approved specs to `CHARACTERS`,
|
||||||
|
build and commit their GLBs, update the manifest, and extend the role pools.
|
||||||
|
5. **QA and integration.** Run the 24-NPC budget, portrait baseline, gear fit,
|
||||||
|
headless suite and release build. Record provenance and any accepted limits.
|
||||||
|
Only then update `CHANGELOG.md` and `docs/development-status.md`.
|
||||||
|
|
||||||
|
### Adapter notes
|
||||||
|
|
||||||
|
- Apply node transforms before measuring or exporting. CesiumMan imports with a
|
||||||
|
Z-up root and an `Icosphere` helper object; exclude helpers, not just the mesh
|
||||||
|
names, when measuring.
|
||||||
|
- Weld duplicate seam vertices before decimating. Quaternius' 12k imported
|
||||||
|
vertices are mostly UV/normal splits; the underlying surface is much smaller,
|
||||||
|
but the glTF budget counts the splits after export.
|
||||||
|
- Decimate to a target below 8,000 tris/verts, then re-unwrap only if the
|
||||||
|
existing UVs cannot survive the material remap. The current library uses
|
||||||
|
smart projection at 80° to keep islands large.
|
||||||
|
- Map materials by semantic role, not by source name. A source "Purple" or
|
||||||
|
"Worker_Vest" needs an explicit mapping to `cloth`, `pants`, `skin`, `hair` or
|
||||||
|
`accessory`; do not rely on colour heuristics.
|
||||||
|
- Keep the face direction unambiguous. The front is Blender −Y; verify it with
|
||||||
|
a capture, not by guessing from the source file.
|
||||||
|
- For any future external-rig path, do not silently add a second skeleton format
|
||||||
|
to `NpcAvatar`. Record the per-model skeleton, animation source and gear
|
||||||
|
offsets as an explicit contract.
|
||||||
|
|
||||||
|
## Review outcome
|
||||||
|
|
||||||
|
The project owner answered the review questions on 2026-11-03:
|
||||||
|
|
||||||
|
| Question | Answer |
|
||||||
|
| --- | --- |
|
||||||
|
| Are third-party CC0 characters acceptable? | Yes. |
|
||||||
|
| Which direction? | Prototype Quaternius and extend the procedural generator. |
|
||||||
|
| Which looks first? | Crowd variety. |
|
||||||
|
| Should MakeHuman be pursued? | Evaluate it for a later, separate local development pipeline. |
|
||||||
|
| Commit the previews? | Yes, with CC-BY attribution. |
|
||||||
|
|
||||||
|
## Downloaded example provenance
|
||||||
|
|
||||||
|
Source models were downloaded on 2026-11-03 and kept out of version control. The two
|
||||||
|
research preview sheets are committed under `docs/blender_previews/`; the CC-BY
|
||||||
|
attribution is recorded in `CREDITS.md`.
|
||||||
|
|
||||||
|
| File | Source | SHA-256 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `quaternius/Casual_Hoodie.glb` | `raw.githubusercontent.com/LuanDucate/Ducz.CharacterCreator/HEAD/public/models/Male/Casual_Hoodie.glb` (Quaternius CC0) | `f5b722199f7628b24550989af4230f65d5230727df9c326fd2b3e360effe5249` |
|
||||||
|
| `quaternius/Worker.glb` | same mirror, `Male/Worker.glb` | `8d0f454336c2effc5c08f63a177af3a82d1a7af025ca9588b40e4c90c4703534` |
|
||||||
|
| `quaternius/Casual.glb` | same mirror, `Female/Casual.glb` | `bc2d84fba249ebaa0629cb9d133ad37234196e4b8004aa205a6397abed04e05c` |
|
||||||
|
| `kenney_blocky_characters.zip` | `kenney.nl/assets/blocky-characters` | `5e123859aa0c1598342b600c6db197024a1d63eb9ec531398b310725f589887e` |
|
||||||
|
| `khronos/CesiumMan.glb` | `github.com/KhronosGroup/glTF-Sample-Assets` | `b7001eaeea8254bd44773bcd247e78696d94169388fbb2a1800fc69434e777d9` |
|
||||||
|
| `khronos/RiggedFigure.glb` | same repository | `d6be85417d3e256861ee733eea6916093a7af7c79c16366181fd8abcaeb38cf5` |
|
||||||
|
| `makehuman/base.obj` | `github.com/makehumancommunity/makehuman/blob/master/makehuman/data/3dobjs/base.obj` | `8e761e6624b8f54536409135d1636da63b32486a90d4897f84e121d144f6fb4c` |
|
||||||
|
|
||||||
|
Source terms:
|
||||||
|
|
||||||
|
- Quaternius packs are CC0 1.0 according to the pack page and the mirror's
|
||||||
|
credits. The mirror is a convenience download; the original pack page is
|
||||||
|
<https://quaternius.com/packs/ultimatemodularcharacters.html>.
|
||||||
|
- Kenney Blocky Characters 2.0 is CC0 1.0; the pack's `License.txt` is included
|
||||||
|
in the download.
|
||||||
|
- CesiumMan and RiggedFigure are CC BY 4.0, © 2017 Cesium. If any derivative is
|
||||||
|
committed or shipped, the attribution must be added to `CREDITS.md`.
|
||||||
|
- MakeHuman core assets are CC0 per `LICENSE.ASSETS.md`; the application source
|
||||||
|
is AGPL and is not bundled.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# NPC proportion audit
|
||||||
|
|
||||||
|
Status: audited 2026-11-02. All eight library variants are inside the stylized
|
||||||
|
proportion guide; no geometry correction was required in this pass. The report
|
||||||
|
is repeatable from the exported GLBs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/npc_proportions.py report
|
||||||
|
python3 tools/npc_proportions.py report --json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
`tools/npc_proportions.py` reads the GLB meshes and rest-pose joint hierarchy:
|
||||||
|
|
||||||
|
- height from the full mesh bounds;
|
||||||
|
- head height, width and aspect from `Head`/`skin` vertices (hair excluded so
|
||||||
|
long hairstyles do not change the head measurement);
|
||||||
|
- shoulder silhouette from vertices in the upper-chest band around the
|
||||||
|
shoulder joint;
|
||||||
|
- hip width from `HipL`/`HipR` pants vertices;
|
||||||
|
- torso length from the Spine joint to the shoulder joint;
|
||||||
|
- leg length from the Spine joint to the lowest vertex;
|
||||||
|
- arm length from the shoulder joint to the Hand joint;
|
||||||
|
- foot length from the forward extent of the KneeL/KneeR shoe vertices.
|
||||||
|
|
||||||
|
All ratios are divided by total standing height except `head_aspect`
|
||||||
|
(width / head height).
|
||||||
|
|
||||||
|
## Guide
|
||||||
|
|
||||||
|
These are stylized-but-credible bands for the game's low-poly adults, not
|
||||||
|
photoreal anatomy. The broad build is allowed a larger shoulder/hip silhouette
|
||||||
|
than the slim build.
|
||||||
|
|
||||||
|
| Ratio | Target | Notes |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| `head_height` | 0.16–0.22 | deliberately larger than a realistic 0.13–0.15 |
|
||||||
|
| `head_aspect` | 0.70–1.00 | width / height; rounded heads are acceptable |
|
||||||
|
| `shoulder_width` | 0.28–0.38 | silhouette includes sleeves and build scaling |
|
||||||
|
| `hip_width` | 0.17–0.23 | pants-only width, not skirt/coat flare |
|
||||||
|
| `torso_length` | 0.28–0.38 | Spine joint to shoulder |
|
||||||
|
| `leg_length` | 0.40–0.48 | Spine joint to ground |
|
||||||
|
| `arm_length` | 0.28–0.38 | shoulder to hand |
|
||||||
|
| `foot_length` | 0.12–0.18 | shoe forward extent |
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
| Character | Height | Head | Aspect | Shoulder | Hip | Torso | Leg | Arm | Foot | Result |
|
||||||
|
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
|
||||||
|
| `npc_casual` | 1.824 m | 0.190 | 0.907 | 0.321 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
||||||
|
| `npc_jacket` | 1.824 m | 0.190 | 0.907 | 0.333 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
||||||
|
| `npc_coat` | 1.824 m | 0.190 | 0.907 | 0.328 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
||||||
|
| `npc_vest` | 1.819 m | 0.191 | 0.907 | 0.322 | 0.209 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
|
||||||
|
| `npc_dress` | 1.825 m | 0.190 | 0.907 | 0.289 | 0.191 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
||||||
|
| `npc_hoodie` | 1.856 m | 0.187 | 0.907 | 0.328 | 0.206 | 0.302 | 0.442 | 0.323 | 0.141 | PASS |
|
||||||
|
| `npc_firefighter` | 1.819 m | 0.191 | 0.907 | 0.358 | 0.223 | 0.308 | 0.451 | 0.330 | 0.148 | PASS |
|
||||||
|
| `npc_guard` | 1.819 m | 0.191 | 0.907 | 0.369 | 0.219 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
|
||||||
|
|
||||||
|
No variant is outside a band, so the body pass does not need a global skeleton
|
||||||
|
or mesh rescale. The values closest to a boundary are:
|
||||||
|
|
||||||
|
- **head height 0.190** against the 0.22 upper bound, and **head aspect 0.907**
|
||||||
|
against 1.00 — the head is deliberately large. Any head-size change must be
|
||||||
|
coordinated with the face/headgear pass and its gear-fit measurements.
|
||||||
|
- **guard shoulder 0.369** and **firefighter hip 0.223** against the broad-build
|
||||||
|
upper bounds — these are the intended uniform/turnout silhouettes, not
|
||||||
|
outliers.
|
||||||
|
- **leg length 0.449** and **torso 0.307** sit inside the guide for all variants;
|
||||||
|
the earlier short-leg impression came from the hip joint position, not the
|
||||||
|
visible pelvis line.
|
||||||
|
|
||||||
|
## Follow-up
|
||||||
|
|
||||||
|
The proportion audit does not replace the visual criteria in
|
||||||
|
[`npc-visual-review.md`](npc-visual-review.md): neck/shoulder/hip transitions,
|
||||||
|
face depth, hand grips, clothing hems and shoes still need the appearance pass.
|
||||||
|
Re-run the audit after any body mesh or skeleton change and record the new
|
||||||
|
ratios here.
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# NPC visual baseline and acceptance criteria
|
||||||
|
|
||||||
|
Status: **accepted 2026-11-02 for the 0.9.4 stylized target.** The P1 fixes
|
||||||
|
(tourist photo-prop visibility, backpack straps), neck/shoulder and torso/hip
|
||||||
|
transitions, face depth/detail, hand and gear placement, headgear clearance and
|
||||||
|
shoe/boot silhouettes are applied. The baseline remains the comparison reference;
|
||||||
|
the accepted limitations are listed below. Comparison sheets:
|
||||||
|
[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
|
||||||
|
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg),
|
||||||
|
the latest [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg),
|
||||||
|
the face-detail pass [`sheet_npc_baseline_compare-face-detail.jpg`](blender_previews/sheet_npc_baseline_compare-face-detail.jpg),
|
||||||
|
the conversation-distance hand pass
|
||||||
|
[`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg), and the
|
||||||
|
forward-grip pass [`sheet_npc_hands_grip_compare.jpg`](blender_previews/sheet_npc_hands_grip_compare.jpg).
|
||||||
|
|
||||||
|
The 2026-11-03 skill-guided re-evaluation, its measurements (texel density, budgets, imported
|
||||||
|
bounds) and the open scorecard live in [`3d-model-review.md`](3d-model-review.md). It does not
|
||||||
|
change the accepted limits below.
|
||||||
|
|
||||||
|
## What was captured
|
||||||
|
|
||||||
|
`src/test/capture_npc_baseline.gd` stages all eight library characters in a
|
||||||
|
neutral studio and captures six views per character:
|
||||||
|
|
||||||
|
| View | Camera | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `front` | 1.85 m, 32° FOV, head/upper body | face, hair, collar, shoulders |
|
||||||
|
| `three_quarter` | 1.85 m, yaw 35° | cheek/jaw, ear, profile transition |
|
||||||
|
| `profile` | 1.85 m, yaw 90° | head depth, nose, ear, chest silhouette |
|
||||||
|
| `back` | 1.85 m, yaw 180° | hair cap, neck, coat/backpack fit |
|
||||||
|
| `full_body` | 4.2 m, 38° FOV | proportions, hands, shoes, hem |
|
||||||
|
| `crowd` | 8.0 m, 45° FOV | silhouette read at normal crowd distance |
|
||||||
|
|
||||||
|
The stage is deliberately separate from the venue review harness: a mid-grey
|
||||||
|
solid background, ground plane, one key light and one cool fill light, no HUD,
|
||||||
|
no crowd, no flashlight, no phase lighting and no animation. Front is Godot +Z,
|
||||||
|
matching `NpcAvatar.yaw_towards()`. The camera orbits at the recorded yaw rather
|
||||||
|
than rotating the avatar, so the pose and foot contact stay fixed between views.
|
||||||
|
The full command is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||||||
|
--out=res://tmp/npc-baseline
|
||||||
|
python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
|
||||||
|
```
|
||||||
|
|
||||||
|
The 48 raw PNGs are local QA under `tmp/` (gitignored). The committed contact
|
||||||
|
sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg).
|
||||||
|
For the P2 hand/grip check there is an optional eighth view, captured separately
|
||||||
|
so the 48-frame baseline stays stable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||||||
|
--out=res://tmp/npc-hands --view=hand
|
||||||
|
```
|
||||||
|
|
||||||
|
That writes eight `*__hand.png` frames at 0.80 m from the right hand; the
|
||||||
|
before/after review image is
|
||||||
|
[`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg).
|
||||||
|
The capture is deterministic apart from engine rasterisation; re-running into
|
||||||
|
the same directory keeps the earlier files and writes `-vN` copies rather than
|
||||||
|
overwriting them.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
The criteria below are for the stylized target, not photorealism. A change must
|
||||||
|
pass every applicable item before a library rebuild is accepted.
|
||||||
|
|
||||||
|
1. **Asset contract:** `npc_base` mesh node, the required bones, five tint slots,
|
||||||
|
front +Z, feet within 3 cm of y=0, 1.6–2.1 m height, at most 8,000 triangles
|
||||||
|
and 8,000 glTF vertices per library character. The frozen `npc_base` fallback
|
||||||
|
keeps 6,000 triangles / 4,000 vertices. Gear remains at most 1,500 triangles.
|
||||||
|
2. **Proportion and silhouette:** head, neck, shoulders, torso, hips, limbs and
|
||||||
|
feet read as one body; front/three-quarter/profile silhouettes agree with the
|
||||||
|
rest of the eight-character library; broad/slim/average build variation is
|
||||||
|
preserved.
|
||||||
|
3. **Face:** at 1.85 m the eyes, iris, brows, nose, mouth and ears are distinct;
|
||||||
|
the head volume has a readable jaw/cheek/chin transition; no face-plane seams,
|
||||||
|
inverted normals or z-fighting. The face direction must remain unambiguous.
|
||||||
|
4. **Hair and headgear:** hair covers the scalp without gaps or scalp-through
|
||||||
|
seams; fringes, buns and beards keep their silhouette. Caps, beanies, hoods,
|
||||||
|
sunglasses and the firefighter helmet clear the brow, eyes and ears. Headgear
|
||||||
|
may sit outside the 1.55–1.81 m face band, but its brim must not cover the
|
||||||
|
eyes.
|
||||||
|
5. **Hands and footwear:** at conversation distance the fingers and thumb read
|
||||||
|
as a hand, not a mitten; hand items contact the palm rather than floating;
|
||||||
|
shoes and boots are grounded with a readable toe/heel silhouette and preserve
|
||||||
|
the responder/firefighter footwear language.
|
||||||
|
6. **Clothing:** collars, cuffs, plackets, waistbands, pockets, badges and
|
||||||
|
reflective bands transition cleanly; no z-fighting or floating detail; coat,
|
||||||
|
dress and skirt hems do not show legs through the fabric during idle, walk,
|
||||||
|
block, sit or turn; the five tint slots and fixed detail colours remain
|
||||||
|
unchanged.
|
||||||
|
7. **Gear:** head gear clears the refined face/hair; the lanyard badge remains
|
||||||
|
readable in front of the jacket; backpack, shoulder bag and SCBA tank straps
|
||||||
|
lie outside the torso and do not read as a second bag; hand items use the
|
||||||
|
authored grip points.
|
||||||
|
8. **Crowd and performance:** the silhouette remains readable at the 8 m crowd
|
||||||
|
frame and the LOD/impostor tiers match the full mesh; the 24-NPC budget stays
|
||||||
|
within the existing 6.94 ms baseline (no regression beyond the measured
|
||||||
|
variance), and no new per-frame material or mesh churn is introduced.
|
||||||
|
|
||||||
|
Asset-space `fit` values are measurements of the untouched GLBs, not a pass/fail
|
||||||
|
result. A negative `x_clearance` means the gear's narrowest face is inside the
|
||||||
|
body reference; a negative `radial_clearance` means it extends beyond the body
|
||||||
|
silhouette (normal for a backpack, suspicious when it is a strap). Runtime
|
||||||
|
`NpcAvatar.GEAR_OFFSETS` is applied after attachment and is not part of the GLB
|
||||||
|
measurement.
|
||||||
|
|
||||||
|
## Proportion audit
|
||||||
|
|
||||||
|
The separate [`npc-proportion-audit.md`](npc-proportion-audit.md) measures
|
||||||
|
height, head size, shoulder/hip width, torso, leg, arm and foot ratios for every
|
||||||
|
variant with `tools/npc_proportions.py`. All eight pass the stylized guide; the
|
||||||
|
head and broad-build shoulders sit nearest their upper bounds. No skeleton or
|
||||||
|
mesh proportion change was needed in this pass.
|
||||||
|
|
||||||
|
## Baseline measurements
|
||||||
|
|
||||||
|
From `python3 tools/blender_assets.py fit` and the manifest:
|
||||||
|
|
||||||
|
| Character | Tris | Verts | Head width | Clothing radius | Notable gear clearances |
|
||||||
|
| --- | ---: | ---: | ---: | ---: | --- |
|
||||||
|
| `npc_casual` | 4,482 | 4,260 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
|
||||||
|
| `npc_jacket` | 4,872 | 4,661 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
|
||||||
|
| `npc_coat` | 5,474 | 5,264 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
|
||||||
|
| `npc_vest` | 4,980 | 4,879 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
|
||||||
|
| `npc_dress` | 4,900 | 4,719 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
|
||||||
|
| `npc_hoodie` | 5,304 | 4,950 | 0.315 m | 0.190 m (hip) | — |
|
||||||
|
| `npc_firefighter` | 5,344 | 5,494 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
|
||||||
|
| `npc_guard` | 5,046 | 5,096 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
|
||||||
|
|
||||||
|
`npc_coat` is widest at 5,474 triangles (2,526 below the 8,000 cap);
|
||||||
|
`npc_firefighter` is closest on vertices at 5,494 (2,506 below the 8,000 cap).
|
||||||
|
The refit backpack measures 380 triangles / 406 vertices.
|
||||||
|
|
||||||
|
## Baseline findings, ranked
|
||||||
|
|
||||||
|
### P1 — fix in the first model pass
|
||||||
|
|
||||||
|
1. **The tourist carried two bag silhouettes. Fixed 2026-11-02.** `_add_props()`
|
||||||
|
created a 0.30 × 0.36 × 0.14 box at `(-0.34, 1.15, -0.18)` for the tourist
|
||||||
|
photo pose while `_add_accessory("backpack")` also attached the library
|
||||||
|
backpack. From the three-quarter/back view the two read as separate bags. The
|
||||||
|
photo prop is now hidden outside `photo`/`film` poses
|
||||||
|
(`photo_prop_visible_for_pose()`), so it only appears when the character is
|
||||||
|
actually taking a picture; the after frame confirms one bag at rest.
|
||||||
|
2. **Face depth and detail. Fixed 2026-11-02.** The sclera was recessed into the
|
||||||
|
socket and wrapped with a thin upper lid; the separate jaw/cheek spheres were
|
||||||
|
replaced by a chin plate and cheekbones sculpted into the head mesh. The nose
|
||||||
|
now has ala wings and a smaller bridge/tip, the mouth is one rounded slot
|
||||||
|
instead of a flat bar, and the ears have a darker concha. That removes the
|
||||||
|
dark intersection rings and the moustache-like read at portrait distance; the
|
||||||
|
low-poly brows remain stylized.
|
||||||
|
3. **Backpack straps read as rigid black bars. Refit 2026-11-02.** The straps
|
||||||
|
were thin round tubes whose chest/waist points sat inside the torso, so only
|
||||||
|
fragments showed from the front. They are now flattened bands whose front path
|
||||||
|
sits outside the chest; the after sheet shows a continuous strap instead of
|
||||||
|
two detached rods. The backpack asset is now 380 triangles / 406 vertices
|
||||||
|
(still far below the 1,500-triangle gear budget).
|
||||||
|
|
||||||
|
### P2 — fix after the first P1 pass
|
||||||
|
|
||||||
|
4. **Hands: geometry and forward offset fixed 2026-11-02; finger curl still
|
||||||
|
open.** The palm is wider and shallower, and each finger is now a
|
||||||
|
two-segment bent tube with a rounded tip and a separate bent thumb. Hand gear
|
||||||
|
is authored around the wrist proxy, so the runtime now moves every hand item
|
||||||
|
`HAND_ITEM_FORWARD` (4 cm) into the open palm; the phone, flashlight, camera,
|
||||||
|
cup and clipboard are no longer buried inside the hand. The fingers are still
|
||||||
|
open, so a true wrap/thumb contact and the umbrella/sign grips remain to be
|
||||||
|
judged.
|
||||||
|
5. **Clothing detail is tile-driven and transitional seams are visible.** The
|
||||||
|
fabric pattern repeats strongly on the casual, coat and dress variants, and
|
||||||
|
collar/cuff/waist edges are abrupt. This is more visible than the shoe/hem
|
||||||
|
details in the current portraits.
|
||||||
|
6. **Shoes: fixed 2026-11-02.** Casual shoes gained rounded toe and heel caps;
|
||||||
|
responder/firefighter boots gained matching toe/heel volumes while keeping the
|
||||||
|
taller black rubber boot language. Both stay above the sole contact plane, and
|
||||||
|
the full-body comparison is `sheet_npc_baseline_compare-shoes.jpg`.
|
||||||
|
7. **Headgear scale and clearance. Checked 2026-11-02.** The cap and helmet
|
||||||
|
width ratios are 1.067 and 1.651; those are expected for headgear. Final-context
|
||||||
|
portraits confirm the cap, firefighter helmet, hood and sunglasses clear the
|
||||||
|
refined brow/eyes, and the helmet brim still reads as deliberate turnout gear.
|
||||||
|
No gear rebuild was needed.
|
||||||
|
8. **Performer props dominate the silhouette.** The red cone hat and the
|
||||||
|
primitive sign are intentionally large stage props, but they should be
|
||||||
|
checked during the pose pass so they do not read as head/body geometry.
|
||||||
|
|
||||||
|
### Accepted limits for the 0.9.4 release
|
||||||
|
|
||||||
|
- The hooded variants intentionally cover part of the face; face readability is
|
||||||
|
judged on the visible lower face and silhouette, not on hidden eyes.
|
||||||
|
- The low-poly style is the target. The acceptance bar is readability and clean
|
||||||
|
construction, not photoreal skin or hair.
|
||||||
|
- Held items use the forward-offset open palm (`HAND_ITEM_FORWARD`) rather than a
|
||||||
|
finger-curl rig. There are no finger bones in the contract; phone, cup,
|
||||||
|
clipboard, camera, flashlight, wand and radio were judged in the hand capture.
|
||||||
|
- Fabric folds are not separate geometry; the current torso, elbow and knee
|
||||||
|
shaping is the accepted stylized construction.
|
||||||
|
- The >26 m impostor remains a two-quad billboard; the LOD tier/hysteresis and
|
||||||
|
near/far swap are covered by `test_avatar_mesh_budget_and_lod_swap`.
|
||||||
|
- NPC fabric textures are 256² with a 4× UV repeat; cloth/pants skip the baked
|
||||||
|
per-character AO to avoid large portrait blotches. Skin, hair and gear keep AO.
|
||||||
|
- Evening readability is represented by the day/night/LATE final-context set;
|
||||||
|
LATE is the venue night variant.
|
||||||
|
|
||||||
|
## Before/after workflow
|
||||||
|
|
||||||
|
Keep the baseline directory, rebuild the same capture into a new directory after
|
||||||
|
the model changes, then produce a comparison sheet. Do not overwrite the
|
||||||
|
committed baseline image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||||||
|
--out=res://tmp/npc-baseline-after
|
||||||
|
python3 tools/npc_baseline.py compare \
|
||||||
|
--before tmp/npc-baseline --after tmp/npc-baseline-after \
|
||||||
|
--out tmp/npc-baseline-compare.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
The comparison sheet places the before/after pair for each view side by side.
|
||||||
|
The committed runs are
|
||||||
|
[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
|
||||||
|
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
|
||||||
|
and [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
|
||||||
|
The transition pass is [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg).
|
||||||
|
The face-detail pass is [`sheet_npc_baseline_compare-face-detail.jpg`](blender_previews/sheet_npc_baseline_compare-face-detail.jpg).
|
||||||
|
The hand pass is [`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg);
|
||||||
|
the forward-grip pass is
|
||||||
|
[`sheet_npc_hands_grip_compare.jpg`](blender_previews/sheet_npc_hands_grip_compare.jpg).
|
||||||
|
The 0.9.5 texture pass adds
|
||||||
|
[`sheet_texture_refinement_0.9.5.jpg`](blender_previews/sheet_texture_refinement_0.9.5.jpg),
|
||||||
|
which pairs the close-range surface and cloth comparisons. Keep the baseline
|
||||||
|
sheet as the before artifact and add a new comparison sheet rather than
|
||||||
|
replacing it.
|
||||||
|
|
||||||
|
## Next implementation order
|
||||||
|
|
||||||
|
0.9.4 and the 0.9.5 texture pass are closed. The next visual work belongs to the
|
||||||
|
0.10.0 acceptance review; keep the NPC baseline/final-context commands as the
|
||||||
|
regression gate for any future character edit.
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
# Development Plan — Graphics Quality v0.6.0 (Blender-authored NPCs)
|
|
||||||
|
|
||||||
> Status: Proposed · Target version: **v0.6.0** (MINOR)
|
|
||||||
> Companion decision: [ADR-0013](adr/0013-blender-authored-npc-characters.md)
|
|
||||||
|
|
||||||
## Why this exists
|
|
||||||
|
|
||||||
The game is functionally complete through v0.5.0, but NPCs are still the
|
|
||||||
procedural capsule-and-box rig from [ADR-0010](adr/0010-procedural-npc-presentation.md).
|
|
||||||
They read as people at conversation distance but not at cinematic distance, and
|
|
||||||
every surface on a body is a flat color. ADR-0010 recorded that "a later
|
|
||||||
skinned-mesh pass will replace this rig and must keep the `Head` contract" —
|
|
||||||
this plan is that pass.
|
|
||||||
|
|
||||||
The goal is **better NPCs first**, then a modest environment lift, using the
|
|
||||||
existing AI tooling for art direction and quality gates, and **Blender** for the
|
|
||||||
geometry, UVs, skeleton, and baked material maps.
|
|
||||||
|
|
||||||
## Constraints that shape the plan
|
|
||||||
|
|
||||||
- **Renderer is GL Compatibility** (`project.godot` `config/features`). SSAO,
|
|
||||||
SSIL, and volumetrics are unavailable. Quality therefore comes from better
|
|
||||||
meshes, baked maps (AO/normal/roughness), and lighting — not from a renderer
|
|
||||||
switch.
|
|
||||||
- **CI builds offline.** `.gitea/workflows/build.yml` runs the Godot editor only
|
|
||||||
(no Blender, no network). Everything the game loads must be committed; the
|
|
||||||
Blender step is a developer tool, and the game keeps a procedural fallback so a
|
|
||||||
fresh checkout still boots and the headless suite still passes.
|
|
||||||
- **Determinism.** Seed-driven variety (`outfit_for`, `layer_for`,
|
|
||||||
`hand_item_for`, `idle_style_for`) must keep working and keep its tests green.
|
|
||||||
AI generation is a dev-time input, not a runtime dependency; the committed
|
|
||||||
assets are the source of truth.
|
|
||||||
- **Frame budget.** v0.5.0 measured 16 avatars = 534 meshes ≈ 6.9 ms headless.
|
|
||||||
A skinned rig replaces ~33 draw calls per avatar with one skin, but skinning
|
|
||||||
costs CPU. Re-measure with `src/test/smoke_npc_budget.gd` before raising
|
|
||||||
`MAX_LIVE_NPCS`.
|
|
||||||
|
|
||||||
## The NPC asset contract (what must not break)
|
|
||||||
|
|
||||||
From `src/game/npc_avatar.gd` and `src/game/guard_controller.gd`:
|
|
||||||
|
|
||||||
- A scene-level node named **`Rig/Head`** — `GuardController` raycasts for it
|
|
||||||
(`get_node_or_null("Rig/Head")`), and the focus circle + name tag + reaction
|
|
||||||
tag attach under it.
|
|
||||||
- Limb pivots currently named `HipL`/`HipR` → `Knee`, `ShoulderL`/`ShoulderR` →
|
|
||||||
`Elbow` → `Hand`. `_animate()` and `_idle_layer()` drive them by `rotation.x`.
|
|
||||||
- Pure static helpers must stay pure and seed-deterministic: `outfit_for`,
|
|
||||||
`layer_for`, `hand_item_for`, `idle_style_for`, `queue_slot_for`,
|
|
||||||
`lod_tier_for_distance`, `reaction_label`, `crouch_for_pose`,
|
|
||||||
`prop_lift_for_pose`, `limb_swing`, `sway_for_pose`.
|
|
||||||
- LOD tiers (`apply_lod`, 0 / 1 / 2 with a billboard impostor at tier 2) must
|
|
||||||
keep working; `_detail_nodes` are hidden at tier 1.
|
|
||||||
- `set_focus_visual`, `_focus_circle`, `_name_tag`, `_reaction_tag`, the
|
|
||||||
theater poses, and the crowd reaction (`start_rubberneck`, `start_flee`) all
|
|
||||||
operate on the rig and must survive the swap.
|
|
||||||
|
|
||||||
## Workstreams
|
|
||||||
|
|
||||||
### Phase 0 — Toolchain spike and contract freeze (small, throwaway)
|
|
||||||
|
|
||||||
1. Confirm headless Blender works in this environment. Blender **5.2.0 LTS**
|
|
||||||
flatpak is installed; the sandbox cwd is `/app/blender`, so `--python` and
|
|
||||||
all input/output paths must be absolute:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
flatpak run org.blender.Blender --background \
|
|
||||||
--python /var/home/zwitschi/Documents/06-games/security-game/tools/npc_pipeline.py -- --build
|
|
||||||
```
|
|
||||||
|
|
||||||
Record the working invocation in `MEMORY.md` (the Im-Dornäcker notes already
|
|
||||||
warn about the cwd gotcha).
|
|
||||||
2. Freeze the asset contract in writing (this document + ADR-0013). No
|
|
||||||
animation clips are exported: `_animate()` and `_idle_layer()` drive the
|
|
||||||
armature bones directly at runtime, so the GLB only needs the skinned rest
|
|
||||||
mesh, the armature, and the material maps.
|
|
||||||
- `assets/models/generated/npc_base.glb` — one skinned mesh (or a small fixed
|
|
||||||
set of sub-meshes: body / head / hands) with a **named armature** and UVs.
|
|
||||||
- Material slots rather than one atlas-per-prop: `skin`, `cloth`,
|
|
||||||
`pants`, `accessory`, `hair` each carry a neutral albedo + normal + ORM
|
|
||||||
(occlusion/roughness/metallic) map. `outfit_for` colors tint the slot, so
|
|
||||||
multi-color outfits and seed variety survive.
|
|
||||||
- Bone names mirror the current node names (`Head`, `HipL/R`, `KneeL/R`,
|
|
||||||
`ShoulderL/R`, `ElbowL/R`, `HandL/R`) so `_animate()` can target them.
|
|
||||||
- Budget: tier-0 base body ≤ ~6,000 triangles, ≤ ~4,000 vertices (revisit
|
|
||||||
after the first `smoke_npc_budget.gd` run).
|
|
||||||
|
|
||||||
### Phase 1 — `tools/npc_pipeline.py`: author + export + audit
|
|
||||||
|
|
||||||
New tool, run through the Blender flatpak, mirroring the `model_pipeline.py`
|
|
||||||
shape (`list` / `build` / `audit`):
|
|
||||||
|
|
||||||
1. **Author** a low-poly base humanoid in Blender: torso, articulated arms/legs,
|
|
||||||
head with a face front (so the focus ring and the "noticed the guard" cue
|
|
||||||
keep a direction), hands, shoes. Keep the current silhouette language
|
|
||||||
(heads, caps, hoods, backpacks, lanyards) so existing `outfit_for`
|
|
||||||
accessories still attach.
|
|
||||||
2. **Rig** it with an armature whose bones are named to match the node contract.
|
|
||||||
Godot 4.3 imports glTF skeletons as `Skeleton3D`; keep the bone rest pose
|
|
||||||
matching the current `_animate()` assumptions (arms hang, legs straight,
|
|
||||||
+Y up, origin at the feet on the floor).
|
|
||||||
3. **UV-unwrap** into a power-of-two layout and **bake** maps in Blender from
|
|
||||||
node materials or a higher-detail source:
|
|
||||||
- neutral albedo (white fabric pattern) per material slot,
|
|
||||||
- normal map (fabric weave, denim, knit),
|
|
||||||
- ORM map (occlusion in R, roughness in G, metallic in B — same packing
|
|
||||||
`model_pipeline.atlas_for` already uses),
|
|
||||||
- ambient occlusion baked into the albedo or the ORM R channel (this is the
|
|
||||||
Compatibility renderer's only AO source).
|
|
||||||
4. **Export** `npc_base.glb` with materials referenced as Godot-importable
|
|
||||||
textures (or export textures alongside and wire them in GDScript — decide in
|
|
||||||
the spike).
|
|
||||||
5. **Audit** (`npc_pipeline.py audit`) enforcing: UVs in [0,1], expected bone
|
|
||||||
names present, triangle/vertex budget, feet at y ≈ 0, no missing material
|
|
||||||
slots. Reuse the checks and the colour-space discipline from
|
|
||||||
`model_pipeline.py` (glTF `baseColorFactor` is linear; albedo textures are
|
|
||||||
sRGB — do not mix them up).
|
|
||||||
6. **Commit** the `.blend` source (`assets/models/npc/npc_base.blend`) so the
|
|
||||||
build is reproducible and editable, plus the generated `.glb` + textures so
|
|
||||||
CI never needs Blender.
|
|
||||||
|
|
||||||
### Phase 2 — AI-assisted texturing (existing tools only)
|
|
||||||
|
|
||||||
Use the tools already in `tools/`, not new services:
|
|
||||||
|
|
||||||
1. `tools/asset_pipeline.py generate` (OpenRouter,
|
|
||||||
`openai/gpt-5-image-mini`) for seamless clothing albedos — shirt, trousers,
|
|
||||||
jacket, hi-vis vest, coat, dress — at the role sizes its `audit` validates.
|
|
||||||
Run `seamless` for tile-edge blending and `audit` to reject wrong sizes.
|
|
||||||
2. `tools/model_pipeline.py`'s palette helpers (`dominant_colors`,
|
|
||||||
`assign_roles`) or a small shared import, to derive per-role outfit palettes
|
|
||||||
from AI concept sheets (`concept`). `outfit_for` keeps determinism: the AI
|
|
||||||
supplies palettes and textures, never per-frame randomness.
|
|
||||||
3. Normal/roughness detail (fabric weave, denim) baked procedurally in Blender,
|
|
||||||
with the AI albedos layered on top.
|
|
||||||
4. Close the loop with `tools/screen_designs.py judge`: regenerate the staged
|
|
||||||
captures and confirm the new NPCs realise the same screen intents (art style
|
|
||||||
is ignored by the judge; layout and readability are not).
|
|
||||||
|
|
||||||
Outputs land under `assets/textures/ai/game_ready/` (or a new
|
|
||||||
`assets/textures/npc/`) and are **committed**.
|
|
||||||
|
|
||||||
### Phase 3 — Godot integration (`npc_avatar.gd`, `materials.gd`)
|
|
||||||
|
|
||||||
1. Add a `_load_model()` path in `NpcAvatar` that instantiates the imported
|
|
||||||
`npc_base.glb` `PackedScene`, exactly like `WorldBuilder._add_prop_model()`
|
|
||||||
loads props. Keep `_build_body()` as the offline/procedural fallback; choose
|
|
||||||
per-avatar with the same "asset or fallback" pattern (record the choice in a
|
|
||||||
`loaded_model` / `fallback_avatar` field for tests).
|
|
||||||
2. Map `outfit_for()` onto the model's material slots (skin/cloth/pants/
|
|
||||||
accessory/hair) so seed variety, `layer_for`, `hand_item_for`, and the
|
|
||||||
archetype color read stay intact. Accessories (cap/hood/backpack/
|
|
||||||
sunglasses/lanyard) keep attaching under `Rig`/`Head` as they do today.
|
|
||||||
3. Add `SurfaceMaterials` recipes for the baked maps — e.g. `npc()` /
|
|
||||||
`skin()` / `cloth()` — so `_material()` gains a textured path and the body
|
|
||||||
stops calling `Materials.flat()`.
|
|
||||||
4. Abstract the animation targets so `_animate()` / `_idle_layer()` write to
|
|
||||||
either the procedural `Node3D` pivots (fallback) or `Skeleton3D` bones
|
|
||||||
(skinned). Keep the existing `rotation.x` math; the abstraction is a thin
|
|
||||||
`_set_limb_rotation(part, axis, radians)` wrapper.
|
|
||||||
5. Keep `Rig/Head` as a real scene node (a marker following the head bone, or
|
|
||||||
the imported bone's Godot node) so focus, nametag, and head-turn still find
|
|
||||||
it. Re-verify the focus cone smoke and `test_npc_character` /
|
|
||||||
`test_npc_theater` / `test_target_cycle`.
|
|
||||||
6. Re-measure `smoke_npc_budget.gd`; adjust `LOD_TIERS` if the skinned rig is
|
|
||||||
heavier up close. The impostor path is unchanged.
|
|
||||||
|
|
||||||
### Phase 3.5 — Blender asset library (characters, gear, props)
|
|
||||||
|
|
||||||
The library generator is `tools/blender_assets.py` (flatpak Blender, headless).
|
|
||||||
**Status: integrated** — `NpcAvatar` selects characters/gear and `WorldBuilder`
|
|
||||||
loads the replacement/new props plus phase-driven emissives; the procedural rig
|
|
||||||
and primitive props remain fallbacks. Integration notes:
|
|
||||||
[`blender-assets.md`](blender-assets.md).
|
|
||||||
|
|
||||||
**Outputs and contract**
|
|
||||||
|
|
||||||
| Kind | Path | Notes |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Characters | `assets/models/blender/npc/<name>.glb` | 8 variants: `npc_casual`, `npc_jacket`, `npc_coat`, `npc_vest`, `npc_dress`, `npc_hoodie`, `npc_firefighter`, `npc_guard` |
|
|
||||||
| Gear | `assets/models/blender/npc_gear/<name>.glb` | 18 pieces; authored in attach-point space (`head`, `hand`, `back`, `chest`) |
|
|
||||||
| Props | `assets/models/blender/props/<name>.glb` | 20 pieces; the first nine replace current generated/primitive street furniture, the rest are new venue dressing |
|
|
||||||
| Manifest | `assets/models/blender/manifest.json` | path, category, triangles, vertices, bounds, materials, roles, tint slots, attach points |
|
|
||||||
| Sources | `assets/models/blender_src/<name>.blend` | committed, editable source |
|
|
||||||
| Previews | `docs/blender_previews/sheet_<category>.jpg` (committed) + per-asset `*.png` (local) | `render` / `sheet` for visual review |
|
|
||||||
|
|
||||||
All characters reuse the `npc_base` contract: mesh node `npc_base`, bones
|
|
||||||
`Head/HipL/R/KneeL/R/ShoulderL/R/ElbowL/R/HandL/R` (+ Hips/Spine/Neck),
|
|
||||||
material slots `skin/cloth/pants/accessory/hair`, front toward Blender `-Y`
|
|
||||||
(Godot `+Z`), feet at `y ~= 0`. Gear origins are the attach points (head at
|
|
||||||
`Rig/Head`, hand at the hand proxy, back at the spine, chest at the sternum).
|
|
||||||
Props are grounded except `wall_lantern` and `marquee`; both may be emissive.
|
|
||||||
|
|
||||||
**Integration steps**
|
|
||||||
|
|
||||||
1. **Audit the finished build.** `python3 tools/blender_assets.py audit` and
|
|
||||||
`sheet`; fix assets or reconcile the budgets. **Done 2026-11-01:** the
|
|
||||||
characters were simplified to fit the unchanged 6,000 / 4,000 contract
|
|
||||||
(3,788–4,640 tris), and all 46 assets audit clean.
|
|
||||||
2. **Commit the library and sources.** Add `assets/models/blender/**`,
|
|
||||||
`assets/models/blender_src/**`, the generator, manifest, previews, and
|
|
||||||
`docs/blender-assets.md`; generated folders that CI can rebuild offline may
|
|
||||||
stay ignored. `blender_assets.py audit` must work without Blender.
|
|
||||||
3. **Select characters by role.** Extend `NpcAvatar` to pick a variant from the
|
|
||||||
manifest using the profile's role and `outfit_for`/`layer_for` (casual,
|
|
||||||
jacket, coat, vest, dress, hoodie, firefighter, guard). Fall back to
|
|
||||||
`npc_base.glb`, then to the procedural rig, and record the chosen path.
|
|
||||||
4. **Tint and anchor.** Apply `outfit_for` to every variant's five slots via
|
|
||||||
`SurfaceMaterials.npc_slot()`; keep `Rig/Head` for the focus circle, name tag,
|
|
||||||
reaction tag, and accessories. Keep the walk/idle/escalation bone-driving
|
|
||||||
wrapper.
|
|
||||||
5. **Replace procedural gear.** Use the `npc_gear` catalogue for caps, hoods,
|
|
||||||
helmets, backpacks, lanyards, hand items (phone, cup, clipboard, umbrella,
|
|
||||||
camera, sign, flashlight, wand, radio) and the SCBA tank; attach at the
|
|
||||||
documented points and fall back to the current primitives when an asset is
|
|
||||||
missing.
|
|
||||||
6. **Replace and extend props.** Load the replacement set through
|
|
||||||
`WorldBuilder` (`bollard`, `bench`, `trash_bin`, `planter_box`,
|
|
||||||
`newspaper_box`, `fire_hydrant`, `street_lamp`, `street_tree`,
|
|
||||||
`traffic_cone`), then place the new dressing (`palm_tree`, `round_planter`,
|
|
||||||
`a_frame_sign`, `wall_lantern`, `crowd_barrier`, `stanchion`, `handrail`,
|
|
||||||
`security_podium`, `double_door`, `marquee`, `sedan`) using the existing
|
|
||||||
placement table/collider/avoidance contract. No prop may enter the
|
|
||||||
door/steps/guest lane.
|
|
||||||
7. **Wire emissives to the phase.** Street-lamp bulbs, wall lanterns, the door
|
|
||||||
lock LED, marquee bulbs/sign face, and sedan headlights should follow the
|
|
||||||
existing day/night lighting rather than staying always on.
|
|
||||||
8. **Measure and gate.** Re-run `smoke_npc_budget.gd` with the new characters
|
|
||||||
before raising `MAX_LIVE_NPCS`; add a prop triangle/draw budget check. Then
|
|
||||||
recapture `docs/screenshots/` and run `screen_designs.py compare` + `judge`
|
|
||||||
plus a close-up target shot for character readability.
|
|
||||||
9. **Fallbacks stay offline.** A fresh checkout with no Blender and no network
|
|
||||||
must boot on the committed library (or `npc_base.glb`/procedural rig when an
|
|
||||||
asset is absent); CI still runs the Godot import and headless suite.
|
|
||||||
|
|
||||||
|
|
||||||
### Phase 4 — Environment and lighting lift (after NPCs land) ✅
|
|
||||||
|
|
||||||
1. **Baked AO on props:** use the Phase 3.5 Blender prop replacements
|
|
||||||
(`street_lamp`, `street_tree`, `bench`, …) and bake AO into their
|
|
||||||
albedo/ORM maps so contact shadows read without SSAO. Keep the same
|
|
||||||
`WorldBuilder` load/fallback contract (ADR-0012's follow-up).
|
|
||||||
2. **Higher-resolution environment textures:** regenerate brick/concrete/asphalt
|
|
||||||
through `asset_pipeline.py` with normal maps, keeping the current UV scales;
|
|
||||||
use the same sRGB/albedo + packed ORM discipline as the library assets.
|
|
||||||
3. **Evaluate a desktop-only Forward+ preset** — done: it boots on the desktop Vulkan
|
|
||||||
driver, but the renderer is project-wide and the Web preset must stay GL
|
|
||||||
Compatibility. v0.6.0 keeps Compatibility; a desktop-only override is deferred.
|
|
||||||
4. Regenerate `docs/screenshots/` with `capture_screenshots.gd`, then run
|
|
||||||
`screen_designs.py compare` and `judge` as the regression gate, plus
|
|
||||||
`blender_assets.py sheet` for the character/gear/prop library.
|
|
||||||
|
|
||||||
### Phase 5 — Verification and release
|
|
||||||
|
|
||||||
- Headless: fallback build, `npc_pipeline.py audit` (via `test_model_pipeline`
|
|
||||||
style Python tests if Blender is present, otherwise skip with reason), outfit
|
|
||||||
determinism, `Rig/Head` presence, material recipe assertions.
|
|
||||||
- Windowed smokes: `smoke_npc_budget.gd`, `smoke_movement.gd`,
|
|
||||||
`smoke_focus_radial.gd`, `smoke_props.gd`; screenshot comparison.
|
|
||||||
- Bump `config/version` to `0.6.0`, update `README.md`, `docs/architecture.md`
|
|
||||||
(Level 2 gains `tools/npc_pipeline.py`), and publish per
|
|
||||||
[`docs/RELEASING.md`](RELEASING.md).
|
|
||||||
|
|
||||||
## Risks and mitigations
|
|
||||||
|
|
||||||
| Risk | Mitigation |
|
|
||||||
| --- | --- |
|
|
||||||
| Blender flatpak sandbox paths (cwd `/app/blender`) | Absolute paths everywhere; record the invocation in `MEMORY.md`. |
|
|
||||||
| glTF colour space (linear factors vs sRGB textures) | Reuse `model_pipeline.srgb_to_linear`; bake textures as sRGB, keep factors linear; assert in `audit`. |
|
|
||||||
| Skeleton retargeting vs current node math | Keep the bone names + a thin limb-rotation wrapper; fallback rig keeps old math running. |
|
|
||||||
| Frame budget with skinned avatars | Measure first (`smoke_npc_budget.gd`); tune `LOD_TIERS`; impostor path unchanged. |
|
|
||||||
| No SSAO in Compatibility | Bake AO into albedo/ORM; contact-shadow tune in Phase 4. |
|
|
||||||
| AI output nondeterminism | Generation is dev-time only; commit outputs and gate with `audit`/`judge`/`compare`. |
|
|
||||||
| Blender unavailable in CI | Committed `.glb` + textures; procedural fallback; `audit` skipped with reason when Blender is absent. |
|
|
||||||
|
|
||||||
## Acceptance criteria (definition of done)
|
|
||||||
|
|
||||||
1. NPCs and street furniture are Blender-authored meshes with baked maps; the
|
|
||||||
character variants, gear, and props read as people/objects at conversation
|
|
||||||
distance and survive a cinematic close-up.
|
|
||||||
2. `Rig/Head`, the focus cone, nametag, theater poses, crowd reactions, and LOD
|
|
||||||
all still work; all existing NPC tests pass unchanged or are updated with
|
|
||||||
clear reasons.
|
|
||||||
3. The game boots and plays from a fresh checkout with **no** Blender and no
|
|
||||||
network (committed assets + procedural fallback).
|
|
||||||
4. `npc_pipeline.py build` is deterministic and `audit` enforces the contract.
|
|
||||||
5. 212 headless + 60 Python tests stay green (Blender-dependent Python tests
|
|
||||||
skip with a reason when Blender is absent); `npc_pipeline.py audit` and
|
|
||||||
`blender_assets.py audit` pass; NPC-budget and movement smokes pass;
|
|
||||||
screenshot `compare`/`judge` and the library contact sheets show no
|
|
||||||
regressions.
|
|
||||||
6. The library is committed with its generator/manifest/previews, runtime
|
|
||||||
selection and fallbacks are covered by tests, version bumped to v0.6.0,
|
|
||||||
docs and ADR-0013 updated, release published.
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Screen-design judge report
|
||||||
|
|
||||||
|
Date: 2026-11-02
|
||||||
|
Model: automatic cheapest image+JSON selection (`z-ai/glm-5.3-flash` at run time)
|
||||||
|
Command: `python3 tools/screen_designs.py judge`
|
||||||
|
Result: **21/21 match, 0 review, 0 diff**
|
||||||
|
|
||||||
|
The screenshot gallery was regenerated on the current build before judging, including the eight UI-state captures 14–21 added in `src/test/capture_ui_states.gd`.
|
||||||
|
Accepted captures are in [`docs/screens/`](screens/README.md); the committed
|
||||||
|
gallery is in [`docs/screenshots/`](screenshots/README.md).
|
||||||
|
|
||||||
|
The automatic model selection and retry/spend behavior is documented in
|
||||||
|
[`docs/asset-tooling.md`](asset-tooling.md). This report intentionally records
|
||||||
|
the outcome rather than generated captures, which remain local/gitignored.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Screens and Visual Evidence
|
||||||
|
|
||||||
|
Visual references and generated QA evidence for Gig Line.
|
||||||
|
|
||||||
|
- [`concepts/README.md`](concepts/README.md) — AI-assisted realism concept frames
|
||||||
|
- [`designs/README.md`](designs/README.md) — screen-design mockups
|
||||||
|
- [`../screenshots/README.md`](../screenshots/README.md) — committed game gallery
|
||||||
|
- `realism/` — locally generated realism runs (gitignored; no README requirement)
|
||||||
|
|
||||||
|
## Quick preview
|
||||||
|
|
||||||
|
| Screen | Reference | Current capture |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Venue | <img src="designs/venue_overview.png" width="320" alt="Venue design"> | <img src="../screenshots/01-venue-overview.png" width="320" alt="Venue capture"> |
|
||||||
|
| Joke Shop | <img src="concepts/joke_shop/joke_shop_entrance_day.png" width="320" alt="Joke Shop reference"> | <img src="../screenshots/12-joke-shop.png" width="320" alt="Joke Shop capture"> |
|
||||||
|
| TV Studio | <img src="designs/studio_arrival.png" width="320" alt="Studio design"> | <img src="../screenshots/13-studio-arrival.png" width="320" alt="Studio capture"> |
|
||||||
|
| El Capitan | <img src="concepts/hollywood_boulevard/hollywood_boulevard_entrance_day.png" width="320" alt="El Capitan reference"> | <img src="../screenshots/01-venue-overview.png" width="320" alt="El Capitan capture"> |
|
||||||
@@ -1,41 +1,103 @@
|
|||||||
# Scene concepts: the realistic target look
|
# Scene concepts: the realistic target look
|
||||||
|
|
||||||
These are AI-generated example frames (`python3 tools/scene_concepts.py generate --group scene`,
|
These are AI-generated example frames (`python3 tools/scene_concepts.py generate --group scene`, `openai/gpt-5-image-mini`, 1280×720) for the realism pass. They are art direction, not assets. `tools/scene_concepts.py realism` pairs them with the in-game shots from `src/test/capture_realism.gd` (see `SHOTS` in the tool). New generations never overwrite a file here; they are saved as `<name>-vN.png`.
|
||||||
`openai/gpt-5-image-mini`, 1280×720) for the realism pass. They are art direction, not
|
|
||||||
assets. `tools/scene_concepts.py realism` pairs them with the in-game shots from
|
|
||||||
`src/test/capture_realism.gd` (see `SHOTS` in the tool). New generations never overwrite a
|
|
||||||
file here; they are saved as `<name>-vN.png`.
|
|
||||||
|
|
||||||
| Concept | Visual targets it sets |
|
## Real-world Hollywood block
|
||||||
| --- | --- |
|
|
||||||
| `hollywood_entrance_morning` | warm low sun, long soft shadows, brick with visible mortar and wear, stone plinth, potted topiary, haze that fades the far street to beige |
|
|
||||||
| `hollywood_entrance_golden_hour` | strong back light and rim light on people, marquee bulbs reading even in daylight |
|
|
||||||
| `hollywood_entrance_rain_night` | dark scene lit by practical lights only (marquee, sodium lamps), wet glossy ground with reflections, fog |
|
|
||||||
| `boulevard_skyline_day` | deep layered skyline with real towers and haze, palms lining the road, red curb paint, believable asphalt |
|
|
||||||
| `boulevard_skyline_dusk` | purple/orange gradient sky, neon signs, lit windows, car light trails |
|
|
||||||
| `joke_shop_alley_evening` | narrow brick alley, one warm wall lantern as key light, deep shadows, people in dark jackets |
|
|
||||||
| `joke_shop_door_closeup` | close material detail: brick, mortar, aged wood door, brass |
|
|
||||||
| `studio_stage_door_night` | granite facade, a bright bulb canopy as the main light, a glossy black car, stanchions and a barrier |
|
|
||||||
| `sidewalk_material_closeup` | paver joints, stains, granite curb, aggregate asphalt and a tree grate at grazing light |
|
|
||||||
| `entrance_steps_podium` | granite steps, handrails, glass doors, the check-in podium |
|
|
||||||
| `crowd_barrier_line` | steel barriers, a red carpet, a crowd with depth-of-field haze |
|
|
||||||
| `responder_arrival_wide` | fire engine, turnout gear, red/blue light spill on wet pavement |
|
|
||||||
| `npc_portrait_day` | close portrait of a pedestrian: skin with visible pores and stubble, fabric weave, shallow depth of field. The bar for an avatar close-up, not for the crowd shots |
|
|
||||||
|
|
||||||
The `npc_portrait` capture in `capture_realism.gd` is staged, not posed by the
|
The map-optimization references for the El Capitan Entertainment Centre block
|
||||||
crowd: it freezes one NPC on an open patch of sidewalk, parks the rest of the
|
(6840 Hollywood Blvd) are in
|
||||||
crowd out of frame, turns the subject into the camera and stands the guard on the
|
[`hollywood_boulevard/README.md`](hollywood_boulevard/README.md). The real-site
|
||||||
sunlit side. It is the only shot where a face fills the frame, so it is the one to
|
facts and game mapping are documented in
|
||||||
eyeball after an avatar change (`docs/screens/realism/2026-11-01-npc/`).
|
[`../../hollywood-boulevard-map.md`](../../hollywood-boulevard-map.md).
|
||||||
|
|
||||||
Cross-cutting targets, for comparison with the 2026-11-01 captures in
|
## Concept list
|
||||||
`docs/screens/realism/2026-11-01-v2/sheet.jpg`:
|
|
||||||
|
|
||||||
- **Exposure.** The concepts keep the ground mid-grey. The game's sidewalk is close to white,
|
### Hollywood Entrance
|
||||||
so it is overexposed.
|
|
||||||
- **Night means dark.** The night/dusk concepts are lit by practical lights. The game's
|

|
||||||
"Evening" phase is a bright pink-orange frame.
|
warm low sun, long soft shadows, brick with visible mortar and wear, stone plinth, potted topiary, haze that fades the far street to beige
|
||||||
- **Depth.** The concepts show a layered skyline fading into haze. The new v2 skyline strips
|
|
||||||
supply this; the old box skyline did not.
|
### Hollywood Entrance Golden Hour
|
||||||
- **Geometry and foliage.** The facades have plinths, cornices and deep window reveals, and the
|
|
||||||
trees and shrubs are organic. The game uses flat walls and blob trees.
|

|
||||||
|
strong back light and rim light on people, marquee bulbs reading even in daylight
|
||||||
|
|
||||||
|
### Hollywood Entrance Rain Night
|
||||||
|
|
||||||
|

|
||||||
|
dark scene lit by practical lights only (marquee, sodium lamps), wet glossy ground with reflections, fog
|
||||||
|
|
||||||
|
### Boulevard Skyline
|
||||||
|
|
||||||
|

|
||||||
|
deep layered skyline with real towers and haze, palms lining the road, red curb paint, believable asphalt
|
||||||
|
|
||||||
|
### Boulevard Skyline Dusk
|
||||||
|
|
||||||
|

|
||||||
|
purple/orange gradient sky, neon signs, lit windows, car light trails
|
||||||
|
|
||||||
|
### The Joke Shop — Sunset Strip
|
||||||
|
|
||||||
|
The current Joke Shop map is based on The Comedy Store at 8433 Sunset Blvd.
|
||||||
|
The real-site mapping, simplifications and reference photographs are in
|
||||||
|
[`../../joke-shop-map.md`](../../joke-shop-map.md); the frames are in
|
||||||
|
[`joke_shop/README.md`](joke_shop/README.md).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Legacy Joke Shop Alley (pre-map optimization)
|
||||||
|
|
||||||
|

|
||||||
|
narrow brick alley, one warm wall lantern as key light, deep shadows, people in dark jackets
|
||||||
|
|
||||||
|

|
||||||
|
close material detail: brick, mortar, aged wood door, brass
|
||||||
|
|
||||||
|
These two frames describe the old alley layout and are retained for comparison;
|
||||||
|
they are no longer the target for the Joke Shop map.
|
||||||
|
|
||||||
|
### Studio Stage Door Night
|
||||||
|
|
||||||
|

|
||||||
|
granite facade, a bright bulb canopy as the main light, a glossy black car, stanchions and a barrier
|
||||||
|
|
||||||
|
### Sidewalk Material Closeup
|
||||||
|
|
||||||
|

|
||||||
|
paver joints, stains, granite curb, aggregate asphalt and a tree grate at grazing light
|
||||||
|
|
||||||
|
### Entrance Steps Podium
|
||||||
|
|
||||||
|

|
||||||
|
granite steps, handrails, glass doors, the check-in podium
|
||||||
|
|
||||||
|
### Crowd Barrier Line
|
||||||
|
|
||||||
|

|
||||||
|
steel barriers, a red carpet, a crowd with depth-of-field haze
|
||||||
|
|
||||||
|
### Responder Arrival Wide
|
||||||
|
|
||||||
|

|
||||||
|
fire engine, turnout gear, red/blue light spill on wet pavement
|
||||||
|
|
||||||
|
### NPC Portrait Day
|
||||||
|
|
||||||
|

|
||||||
|
close portrait of a pedestrian: skin with visible pores and stubble, fabric weave, shallow depth of field. The bar for an avatar close-up, not for the crowd shots
|
||||||
|
|
||||||
|
The `npc_portrait` capture in `capture_realism.gd` is staged, not posed by the crowd: it freezes one NPC on an open patch of sidewalk, parks the rest of the crowd out of frame, turns the subject into the camera and stands the guard on the sunlit side. It is the only shot where a face fills the frame, so it is the one to eyeball after an avatar change (generate a local run under `docs/screens/realism/`).
|
||||||
|
|
||||||
|
Cross-cutting targets, for comparison with locally generated realism runs:
|
||||||
|
|
||||||
|
- **Exposure.** The concepts keep the ground mid-grey. The game's sidewalk is close to white, so it is overexposed.
|
||||||
|
- **Night means dark.** The night/dusk concepts are lit by practical lights. The game's "Evening" phase is a bright pink-orange frame.
|
||||||
|
- **Depth.** The concepts show a layered skyline fading into haze. The new v2 skyline strips supply this; the old box skyline did not.
|
||||||
|
- **Geometry and foliage.** The facades have plinths, cornices and deep window reveals, and the trees and shrubs are organic. The game uses flat walls and blob trees.
|
||||||
|
|
||||||
|
## Additional previews
|
||||||
|
|
||||||
|

|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Hollywood Boulevard references
|
||||||
|
|
||||||
|
AI-generated art-direction frames for the real El Capitan Entertainment Centre
|
||||||
|
block at 6840 Hollywood Boulevard. They are generated with
|
||||||
|
`tools/scene_concepts.py generate --group scene --only ...` and are not shipped
|
||||||
|
game textures. The geometry mapping and source links are in
|
||||||
|
[`../../../hollywood-boulevard-map.md`](../../../hollywood-boulevard-map.md).
|
||||||
|
|
||||||
|
## Entrance — day
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Entrance — night
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Across the boulevard
|
||||||
|
|
||||||
|

|
||||||
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,23 @@
|
|||||||
|
# The Joke Shop references
|
||||||
|
|
||||||
|
AI-generated art-direction frames for the fictional Joke Shop, based on the
|
||||||
|
real Comedy Store block at 8433 Sunset Boulevard. They are generated with
|
||||||
|
`tools/scene_concepts.py generate --group scene --only ...` and are not shipped
|
||||||
|
game textures. The geometry mapping and source links are in
|
||||||
|
[`../../../joke-shop-map.md`](../../../joke-shop-map.md).
|
||||||
|
|
||||||
|
## Entrance — day
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Entrance — night
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Across Sunset
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The older top-level concepts `joke_shop_alley_evening.png` and
|
||||||
|
`joke_shop_door_closeup.png` are kept as a record of the pre-map-optimization
|
||||||
|
alley design. They no longer describe the current Joke Shop map.
|
||||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,29 @@
|
|||||||
|
# Screens/Designs
|
||||||
|
|
||||||
|
Screen-design mockups used by the comparison and vision-judge gates for the runtime UI.
|
||||||
|
|
||||||
|
## Image previews
|
||||||
|
|
||||||
|
| Preview | File |
|
||||||
|
| --- | --- |
|
||||||
|
| <img src="access_inspection.png" width="280" alt="access_inspection.png"> | [`access_inspection.png`](access_inspection.png) |
|
||||||
|
| <img src="body_camera_timeline.png" width="280" alt="body_camera_timeline.png"> | [`body_camera_timeline.png`](body_camera_timeline.png) |
|
||||||
|
| <img src="crowd_panic.png" width="280" alt="crowd_panic.png"> | [`crowd_panic.png`](crowd_panic.png) |
|
||||||
|
| <img src="emergency_radio.png" width="280" alt="emergency_radio.png"> | [`emergency_radio.png`](emergency_radio.png) |
|
||||||
|
| <img src="escalation_hud.png" width="280" alt="escalation_hud.png"> | [`escalation_hud.png`](escalation_hud.png) |
|
||||||
|
| <img src="joke_shop_queue.png" width="280" alt="joke_shop_queue.png"> | [`joke_shop_queue.png`](joke_shop_queue.png) |
|
||||||
|
| <img src="late_night_avenue.png" width="280" alt="late_night_avenue.png"> | [`late_night_avenue.png`](late_night_avenue.png) |
|
||||||
|
| <img src="movement_precision.png" width="280" alt="movement_precision.png"> | [`movement_precision.png`](movement_precision.png) |
|
||||||
|
| <img src="night_flashlight.png" width="280" alt="night_flashlight.png"> | [`night_flashlight.png`](night_flashlight.png) |
|
||||||
|
| <img src="pause_overlay.png" width="280" alt="pause_overlay.png"> | [`pause_overlay.png`](pause_overlay.png) |
|
||||||
|
| <img src="prop_gallery.png" width="280" alt="prop_gallery.png"> | [`prop_gallery.png`](prop_gallery.png) |
|
||||||
|
| <img src="radial_menu.png" width="280" alt="radial_menu.png"> | [`radial_menu.png`](radial_menu.png) |
|
||||||
|
| <img src="radio_report.png" width="280" alt="radio_report.png"> | [`radio_report.png`](radio_report.png) |
|
||||||
|
| <img src="responder_arrival.png" width="280" alt="responder_arrival.png"> | [`responder_arrival.png`](responder_arrival.png) |
|
||||||
|
| <img src="settings_panel.png" width="280" alt="settings_panel.png"> | [`settings_panel.png`](settings_panel.png) |
|
||||||
|
| <img src="shift_review.png" width="280" alt="shift_review.png"> | [`shift_review.png`](shift_review.png) |
|
||||||
|
| <img src="start_menu.png" width="280" alt="start_menu.png"> | [`start_menu.png`](start_menu.png) |
|
||||||
|
| <img src="studio_arrival.png" width="280" alt="studio_arrival.png"> | [`studio_arrival.png`](studio_arrival.png) |
|
||||||
|
| <img src="targeting_npc.png" width="280" alt="targeting_npc.png"> | [`targeting_npc.png`](targeting_npc.png) |
|
||||||
|
| <img src="venue_overview.png" width="280" alt="venue_overview.png"> | [`venue_overview.png`](venue_overview.png) |
|
||||||
|
| <img src="wand_screening.png" width="280" alt="wand_screening.png"> | [`wand_screening.png`](wand_screening.png) |
|
||||||