Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3acf370fc | ||
|
|
04d211b90b | ||
|
|
a6ccd86f85 | ||
|
|
1ce173e9d1 | ||
|
|
90f189a269 | ||
|
|
fb3e75aba3 | ||
|
|
cd285b3a39 | ||
|
|
68f49253fe | ||
|
|
b012c6a675 | ||
|
|
2a6f29f5d0 | ||
|
|
bb54ef3eec | ||
|
|
11f53f66a3 | ||
|
|
9b65a93587 | ||
|
|
56a14747d4 | ||
|
|
8b1bf736af | ||
|
|
f121167e52 | ||
|
|
140a7ada0f | ||
|
|
5508f5510f | ||
|
|
7baa2c4f54 | ||
|
|
0cd12af994 | ||
|
|
271f10ac4d | ||
|
|
e2f52fbfd1 | ||
|
|
f5fa321a8f | ||
|
|
a2ae9e03c3 | ||
|
|
cff3937c16 | ||
|
|
1ed9e05d9b | ||
|
|
8c69f367b0 | ||
|
|
2a7921bae6 | ||
|
|
b419923637 | ||
|
|
c7fd4b59cc | ||
|
|
3fa3482608 | ||
|
|
1049d8631e | ||
|
|
deb74c53af | ||
|
|
9968bc0a4c | ||
|
|
2e1bb19e2c | ||
|
|
5450dbf88a | ||
|
|
79224c5acf | ||
|
|
f0d4b81239 | ||
|
|
48a9795248 | ||
|
|
a5e62534ce | ||
|
|
cd7697cba4 | ||
|
|
d2f773c7ad | ||
|
|
a3c3a37270 | ||
|
|
8dd4f77901 | ||
|
|
ba444152c9 | ||
|
|
d56f14c6dd | ||
|
|
b297c4543f | ||
|
|
958d3ae0ff | ||
|
|
d1898b3221 | ||
|
|
2e1e4d7f70 | ||
|
|
0b2f124ffe | ||
|
|
cdd2e02564 | ||
|
|
632ec16aaa | ||
|
|
30f240b591 | ||
|
|
58d1e79a12 | ||
|
|
44dba010a7 | ||
|
|
2a53ec9aac | ||
|
|
f0f609fe1b | ||
|
|
b386a21173 | ||
|
|
00bd197173 | ||
|
|
4f1eb1f5bf | ||
|
|
50edc47480 | ||
|
|
b8d8869e03 | ||
|
|
1b554df516 | ||
|
|
5fdda88d46 | ||
|
|
ecd50503e7 | ||
|
|
2b6e774580 | ||
|
|
dee939c2ee | ||
|
|
aec1fb7b69 | ||
|
|
50a48d2256 | ||
|
|
69f4f11633 | ||
|
|
d1fa25629e | ||
|
|
359bb9a8c7 | ||
|
|
36576a023a | ||
|
|
c1ccfd6e72 | ||
|
|
27b8f9a249 | ||
|
|
d5a2c911a6 | ||
|
|
236fbf537f | ||
|
|
588722fe7a | ||
|
|
7f2f06bf42 | ||
|
|
6db3cc0873 | ||
|
|
8938dd918c | ||
|
|
742bfd3898 | ||
|
|
87069a30e8 | ||
|
|
3f5c5ce442 | ||
|
|
28fcd96b59 | ||
|
|
e984129597 | ||
|
|
75dea2e7c2 | ||
|
|
09d07452af | ||
|
|
f5ee2b7408 | ||
|
|
5980d329e8 | ||
|
|
6052ea391b | ||
|
|
4e8665e25b | ||
|
|
b945eb6e8f | ||
|
|
03c13c716a | ||
|
|
0a9c78cb30 | ||
|
|
abe5c3f933 | ||
|
|
140ab42536 | ||
|
|
8bdfd0176e | ||
|
|
4e23cc3ad5 | ||
|
|
8d32a8cac0 | ||
|
|
2445ddab34 | ||
|
|
8cacd510bf | ||
|
|
bf383aa0fa | ||
|
|
4bbc576b7f | ||
|
|
542b1264da | ||
|
|
56f4833c8e | ||
|
|
1b31dfdbe4 | ||
|
|
5158efc467 | ||
|
|
ac0cf35cfb | ||
|
|
6655edb5c6 | ||
|
|
60f389c16b | ||
|
|
3084145031 | ||
|
|
34bca89885 | ||
|
|
b53e8a67f9 | ||
|
|
cdfd57fb65 | ||
|
|
5bab6754c0 | ||
|
|
7dd8251a0c | ||
|
|
8e113a4110 | ||
|
|
32c9c45605 | ||
|
|
e4065c2068 | ||
|
|
e7d733bc93 | ||
|
|
93da916f90 | ||
|
|
e3ec4a356a | ||
|
|
55339f23c1 | ||
|
|
a51dae2c33 | ||
|
|
6824dd76a1 | ||
|
|
fde4aa4d0a | ||
|
|
ebb36cf0cb | ||
|
|
724e8b183f | ||
|
|
e4e1566e55 | ||
|
|
0aacee6518 | ||
|
|
215b843b67 | ||
|
|
53d9dc3f78 | ||
|
|
98f2f95f07 | ||
|
|
a677a95d13 | ||
|
|
1afd97c72d | ||
|
|
6ca8bcf034 | ||
|
|
13eb4e0c87 | ||
|
|
8fac1b47cf | ||
|
|
b79e30e9df | ||
|
|
1e761e5a76 | ||
|
|
3eb2ae5e3c | ||
|
|
888897cac9 | ||
|
|
988d431da8 | ||
|
|
87e1c9e7a5 | ||
|
|
480a1ba05f | ||
|
|
43c17ce3c0 | ||
|
|
03f4d4bc49 | ||
|
|
15f9117517 | ||
|
|
78267918bf | ||
|
|
ea3860a60c | ||
|
|
b721f189c5 | ||
|
|
634b1f75a3 | ||
|
|
36583921cf | ||
|
|
3866b0ae01 | ||
|
|
8c2761427f | ||
|
|
2b07913481 | ||
|
|
768dd94bfc | ||
|
|
f516d3e796 | ||
|
|
f95a685934 | ||
|
|
05cb6a43cb | ||
|
|
343b3348c2 | ||
|
|
ad91e71eeb | ||
|
|
6e04c59589 | ||
|
|
e21ff30783 | ||
|
|
5b306b1c52 | ||
|
|
f09a5e7937 | ||
|
|
383f601bc9 | ||
|
|
c46db76d85 | ||
|
|
1a5f2863b0 | ||
|
|
a6fd77068a | ||
|
|
4ed73fcc7b | ||
|
|
c0ddce79e8 | ||
|
|
0c7c9a2b23 | ||
|
|
b94b87d096 | ||
|
|
5dec59355a | ||
|
|
fe3ba3054e | ||
|
|
88a0793bba | ||
|
|
1d4f718376 | ||
|
|
7a5b902797 | ||
|
|
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 | ||
|
|
659b0e35b2 |
No files matched your search
@@ -0,0 +1,212 @@
|
||||
---
|
||||
name: gig-line-blender-asset-contract
|
||||
description: Use when adding, rebuilding, auditing or fixing any Gig Line Blender-authored character, NPC base, gear, prop or venue geometry. Covers the required 13 core bones (optional extra bones allowed), five tint slots, 24,000/24,000 character budgets, gear attach space, 256² texture/AO rules, headless flatpak Blender commands, optional MCP guidance, and the inspect/audit/fit/proportion/capture gates. Do not use for pure GDScript/UI work or release publishing.
|
||||
compatibility: Gig Line repository checkout. Godot 4.7.2 on PATH; Blender 5.2 LTS via the org.blender.Blender flatpak for build/bake/render. Audit, fit, inspect and proportion checks are stdlib-only.
|
||||
metadata:
|
||||
project: Gig Line
|
||||
domain: 3d-assets
|
||||
version: "1.0.0"
|
||||
triggers: Blender, NPC, character, gear, prop, npc_base, GLB, AO, texel, asset rebuild, fit, audit
|
||||
---
|
||||
|
||||
# Gig Line Blender Asset Contract
|
||||
|
||||
Use this skill for any change that touches:
|
||||
|
||||
- `assets/models/blender/**`
|
||||
- `assets/models/generated/npc_base.glb`
|
||||
- `assets/textures/npc/**`
|
||||
- `tools/blender_assets.py`, `tools/npc_pipeline.py`, `tools/bake_ao.py`,
|
||||
`tools/npc_proportions.py` or `tools/npc_baseline.py`
|
||||
- Blender-authored venue/prop geometry
|
||||
|
||||
The project contract and measured gates override any generic Blender advice.
|
||||
Blender MCP is optional interactive tooling; the headless flatpak CLI remains
|
||||
authoritative and CI never needs Blender or MCP. The `blender-skills` pack is
|
||||
optional, local guidance only, and gitignored. Setup and safety are in
|
||||
`docs/asset-tooling.md#optional-interactive-blender-mcp`.
|
||||
|
||||
## Read first
|
||||
|
||||
From the repository root, read the relevant sources rather than relying on this
|
||||
skill alone:
|
||||
|
||||
1. `AGENTS.md` — required workflow and verification.
|
||||
2. `TODO.md` and `MEMORY.md` — current track, claimed tasks, known gotchas.
|
||||
3. `docs/blender-assets.md` — library contract and command reference.
|
||||
4. `docs/asset-tooling.md` — exact rebuild/bake/capture commands.
|
||||
5. `docs/npc-visual-review.md` and `docs/3d-model-review.md` — accepted limits,
|
||||
scorecards and known blockers.
|
||||
6. `docs/known-limitations.md` — limits that must not be silently “fixed” into
|
||||
contract changes.
|
||||
|
||||
The contract lives in code and docs, not in this skill. If they disagree, stop
|
||||
and resolve the source of truth before editing assets.
|
||||
|
||||
## Non-negotiable contract
|
||||
|
||||
| Area | Rule |
|
||||
| --- | --- |
|
||||
| Mesh root | `npc_base` mesh node, skinned under `Rig`; preserve the limb node names. |
|
||||
| Bones | The 13 required core bones: `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`, `Spine`, `Neck`. Optional extra bones (fingers, face, toes) are allowed; they must not move the core rest positions or break animation proxies. |
|
||||
| Tint slots | `skin`, `cloth`, `pants`, `accessory`, `hair`. |
|
||||
| Orientation | Front is Blender −Y / Godot +Z; feet grounded near y = 0; metres. |
|
||||
| Character budget | **24,000 triangles / 24,000 glTF vertices** per character (`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS`). |
|
||||
| Frozen fallback | `npc_base` remains the low-poly fallback; it shares the 24,000/24,000 cap but is not required to use it. Do not silently lower the cap. |
|
||||
| Gear budget | **1,500 triangles** per gear piece; authored in attach-point space. |
|
||||
| Prop budget | **4,000 triangles** per prop. |
|
||||
| Gear attach | Use the catalogued attach points; verify with `fit`. Runtime `GEAR_OFFSETS` (lanyard forward, firefighter helmet up) and `HAND_ITEM_FORWARD` are intentional, not asset-contract changes. |
|
||||
| Textures | 256² albedo/normal/ORM maps. Cloth and pants use `NPC_FABRIC_UV_SCALE = (4,4,1)` and **skip** baked per-character AO; skin/hair/gear keep baked AO. |
|
||||
| CI | Offline and skill-free. Committed GLBs/AO/maps must make CI pass without Blender or MCP. |
|
||||
| Licensing | No new asset source without ADR-0015: CC0, or CC-BY with attribution in `CREDITS.md`; record provenance. |
|
||||
|
||||
Do not change the renderer, engine baseline, runtime dependency graph or the
|
||||
`npc_base` contract as part of an asset pass.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Preflight and claim
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git log --oneline -5
|
||||
godot --version
|
||||
flatpak run org.blender.Blender --version | head -3
|
||||
```
|
||||
|
||||
Inspect the current asset and the relevant docs. Claim the TODO item with `[-]`
|
||||
before starting. Work on one asset family per commit.
|
||||
|
||||
### 2. Establish a baseline
|
||||
|
||||
Run the checks that do not need Blender first:
|
||||
|
||||
```bash
|
||||
python3 tools/blender_assets.py audit
|
||||
python3 tools/blender_assets.py fit
|
||||
python3 tools/blender_assets.py texels
|
||||
python3 tools/npc_proportions.py report
|
||||
python3 tools/npc_pipeline.py audit
|
||||
```
|
||||
|
||||
For a specific incoming or existing GLB:
|
||||
|
||||
```bash
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/blender_assets.py" -- \
|
||||
inspect --file "$PWD/path/to/asset.glb" --category npc_gear --json
|
||||
```
|
||||
|
||||
Record the before measurements (tris/verts, head/shoulder/hip/leg/arm/foot,
|
||||
fabric texel density, gear clearance). Do not “improve” an asset without a
|
||||
measurable target.
|
||||
|
||||
### 3. Optional Blender-skill routing
|
||||
|
||||
If the local `blender-skills` pack is installed, load `blender-director`, then
|
||||
only the specialists the task needs:
|
||||
|
||||
- character mesh/face/hands: `character-artist`, `retopology`
|
||||
- rig/weights/pose: `rigging`
|
||||
- UVs/texel density: `uv-workflow`
|
||||
- maps/materials: `texture-workflow`, `materials`, `lookdev`
|
||||
- gear/props: `prop-artist`, `hard-surface`
|
||||
- budget/LOD/collision: `asset-optimization`, `lod-pipeline`, `collision-proxy`
|
||||
- final review: `qa-review`
|
||||
|
||||
If MCP is available, use it for interactive part exploration only; mirror the result into a scripted change or local `.blend`, then re-export and run the CLI audit/fit gates. Never make the build, CI, tests or runtime depend on MCP. Setup: `docs/asset-tooling.md#optional-interactive-blender-mcp`.
|
||||
|
||||
### 4. Build or bake
|
||||
|
||||
Characters, gear and library props:
|
||||
|
||||
```bash
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/blender_assets.py" -- build --only <name>
|
||||
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/bake_ao.py" -- --only <name>
|
||||
```
|
||||
|
||||
NPC base and its material maps:
|
||||
|
||||
```bash
|
||||
python3 tools/npc_pipeline.py prepare # only when fabric tiles change
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/npc_pipeline.py" -- build
|
||||
python3 tools/npc_pipeline.py audit
|
||||
```
|
||||
|
||||
Do not overwrite a review image or a promoted texture pick. Use a new `-vN`
|
||||
path and document the promotion.
|
||||
|
||||
### 5. Import and capture
|
||||
|
||||
```bash
|
||||
godot --headless --path . --import
|
||||
godot --headless --path . --script res://src/test/test_runner.gd
|
||||
|
||||
# neutral baseline: 8 variants x 6 views, raw frames stay in tmp/
|
||||
godot --path . --script res://src/test/capture_npc_baseline.gd \
|
||||
-- --out=res://tmp/npc-baseline
|
||||
|
||||
# final-context: 96 frames across both venues and day/night
|
||||
godot --path . --script res://src/test/capture_npc_review.gd \
|
||||
-- --out=res://tmp/npc-review
|
||||
```
|
||||
|
||||
Compare before/after with `tools/npc_baseline.py compare`; do not replace the
|
||||
committed baseline sheet until a fix actually lands. See
|
||||
`references/verification.md` for the full gate matrix.
|
||||
|
||||
### 6. Verify and hand off
|
||||
|
||||
At minimum:
|
||||
|
||||
- `blender_assets.py audit` and `fit` exit 0.
|
||||
- `npc_proportions.py` passes the stylized guide.
|
||||
- `npc_pipeline.py audit` passes if the base/textures changed.
|
||||
- texel density is inside the authored/fabric bands.
|
||||
- No character exceeds 24,000/24,000; gear/prop caps hold.
|
||||
- 24-NPC budget and Web/Compatibility path remain green.
|
||||
- Contract tests, headless suite, capture gates and docs check pass.
|
||||
- The diff contains only the intended assets, tools, tests and docs.
|
||||
|
||||
State in the handoff: files changed, measurements before/after, skills used,
|
||||
gates run, and any accepted limit.
|
||||
|
||||
## Failure playbook
|
||||
|
||||
- **`Class "..." hides a global script class`** — a stray `.gd` backup is in the
|
||||
project. Delete `tmp/*.gd` backups, `rm -rf .godot`, re-import; never keep
|
||||
`class_name` copies in `tmp/`.
|
||||
- **Blender cannot see the path** — the flatpak cwd is `/app/blender`; use
|
||||
absolute paths (`"$PWD/..."`).
|
||||
- **Empty or stale captures** — close modals/pause, check the capture script's
|
||||
settle frames, and remove stale output directories before re-running.
|
||||
- **Pose harness mismatch** — the current `capture_npc_pose.gd` is blocked per
|
||||
`docs/3d-model-review.md`; do not use its frames as pose evidence until the
|
||||
recipe is fixed.
|
||||
- **Asset test fails after import** — re-run audit/fit first, then import; if the
|
||||
manifest or AO path changed, rebuild the affected asset rather than editing
|
||||
the manifest by hand.
|
||||
- **Uncertain about a visual change** — produce a labelled before/after sheet and
|
||||
ask for review; do not hide a regression behind a budget claim.
|
||||
|
||||
## Bundled references
|
||||
|
||||
- [`references/asset-contract.md`](references/asset-contract.md) — full contract,
|
||||
inventory, file locations and provenance rules.
|
||||
- `docs/asset-tooling.md#optional-interactive-blender-mcp` — optional MCP installation, connection, safety and
|
||||
part-workflow instructions.
|
||||
- [`references/verification.md`](references/verification.md) — command matrix,
|
||||
capture gates, expected results and acceptance criteria.
|
||||
|
||||
## Do not
|
||||
|
||||
- Make the build, CI, tests or runtime depend on MCP, Blender or API calls.
|
||||
- Change the bone/tint/contract to make a generic asset fit.
|
||||
- Rewrite committed baselines or overwrite existing images.
|
||||
- Commit `.blend` sources, raw captures or generated AI inputs.
|
||||
- Treat an asset audit as a substitute for visual review at portrait and crowd
|
||||
distance.
|
||||
@@ -0,0 +1,141 @@
|
||||
# Asset Contract Reference
|
||||
|
||||
This is a working summary. The authoritative sources remain
|
||||
`docs/blender-assets.md`, `docs/asset-tooling.md`, ADR-0013, the tests in
|
||||
`src/test/tests/` and the constants in `tools/blender_assets.py` /
|
||||
`tools/npc_pipeline.py`.
|
||||
|
||||
## Library inventory
|
||||
|
||||
- **Character variants (8):** `npc_casual`, `npc_jacket`, `npc_coat`,
|
||||
`npc_vest`, `npc_dress`, `npc_hoodie`, `npc_firefighter`, `npc_guard`.
|
||||
- **Frozen fallback:** `npc_base` / `assets/models/generated/npc_base.glb`.
|
||||
- **Gear:** 18 attach-space pieces covering head, back, chest and hand items.
|
||||
- **Library props:** 20 Blender-authored props plus 6 generated props.
|
||||
- **Venues:** procedural venues in `src/game/world_builder.gd`; Blender venue
|
||||
geometry follows the same budgets and collision/provenance rules.
|
||||
|
||||
## Skeleton and geometry
|
||||
|
||||
The required contract skeleton has 13 core bones:
|
||||
|
||||
```text
|
||||
Head, Spine, Neck,
|
||||
HipL, HipR,
|
||||
KneeL, KneeR,
|
||||
ShoulderL, ShoulderR,
|
||||
ElbowL, ElbowR,
|
||||
HandL, HandR
|
||||
```
|
||||
|
||||
Optional extra bones (fingers, face controls, toes) are allowed if they do not
|
||||
move these core rest positions and do not break `NpcAvatar`'s animation proxies.
|
||||
|
||||
Requirements:
|
||||
|
||||
- `npc_base` mesh node exists and is skinned under `Rig`.
|
||||
- Limb node names stay stable for `NpcAvatar` and tests.
|
||||
- Front is Blender −Y / Godot +Z.
|
||||
- Feet are grounded at approximately y = 0; no floating origin.
|
||||
- Units are metres; character height is approximately 1.8 m.
|
||||
- No zero-area/duplicate degenerate faces in the exported mesh.
|
||||
- Every character has UVs in [0,1] unless a planned UDIM/atlas change is
|
||||
explicitly accepted.
|
||||
|
||||
## Material slots
|
||||
|
||||
Exactly these five tint slots:
|
||||
|
||||
```text
|
||||
skin, cloth, pants, accessory, hair
|
||||
```
|
||||
|
||||
- Neutral albedo is authored in Blender; runtime tinting is authoritative.
|
||||
- Slot names are contract values; do not rename or add a sixth slot casually.
|
||||
- The procedural fallback (`NpcAvatar._build_body`) must keep working when the
|
||||
GLB is missing.
|
||||
|
||||
## Budgets
|
||||
|
||||
| Asset | Limit |
|
||||
| --- | --- |
|
||||
| Character (library and base fallback) | 24,000 triangles / 24,000 glTF vertices |
|
||||
| Gear piece | 1,500 triangles |
|
||||
| Prop | 4,000 triangles |
|
||||
| Library prop/venue total | must stay inside the venue tests and library budget |
|
||||
|
||||
`CHAR_MAX_TRIS`, `CHAR_MAX_VERTS`, `GEAR_MAX_TRIS`, `PROP_MAX_TRIS` and related
|
||||
constants live in `tools/blender_assets.py`. The budget is per character, not an
|
||||
average. Measure after every mesh change. Do not lower the cap to make a diff
|
||||
pass.
|
||||
|
||||
## Gear and hand items
|
||||
|
||||
- Gear is authored in the attach-point space documented by `GEAR` /
|
||||
`GEAR_ATTACH` / `ATTACH_BLENDER`.
|
||||
- `blender_assets.py fit` measures gear boxes against the character's head band,
|
||||
clothing radius and back clearance.
|
||||
- Runtime offsets are intentional:
|
||||
- `NpcAvatar.GEAR_OFFSETS = {"lanyard": Vector3(0,0,0.04),
|
||||
"firefighter_helmet": Vector3(0,0.03,0)}`
|
||||
- `NpcAvatar.HAND_ITEM_FORWARD = Vector3(0,0,0.04)`
|
||||
- An offset is **not** an authored-asset change; do not bake it into the GLB
|
||||
without a separate contract decision.
|
||||
- Open-palm grip (no finger-curl rig) is the accepted stylized limit.
|
||||
|
||||
## Textures and AO
|
||||
|
||||
- NPC maps are 256² albedo + normal + packed ORM.
|
||||
- `TEX_SIZE = 256` in `tools/npc_pipeline.py`.
|
||||
- Fabric UV scale is `Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`.
|
||||
- Cloth/pants skip the per-character baked AO; skin/hair/gear keep it. This
|
||||
avoids the large blotchy magnification that appeared at 256².
|
||||
- Gear/props may have their own AO maps; `tools/bake_ao.py` writes
|
||||
`<name>_ao.png` next to the GLB and Godot wires it into the AO channel.
|
||||
- Never overwrite an existing committed image; use a new `-vN` candidate and
|
||||
promote only after review.
|
||||
- Run `python3 tools/asset_pipeline.py audit <path>` and the relevant
|
||||
`npc_pipeline.py audit` before committing map changes.
|
||||
|
||||
## File map
|
||||
|
||||
| Path | Role |
|
||||
| --- | --- |
|
||||
| `tools/blender_assets.py` | Catalogue, builders, audit/fit/inspect/texels/sheet. |
|
||||
| `tools/npc_pipeline.py` | `npc_base` builder, texture preparation and audit. |
|
||||
| `tools/bake_ao.py` | Blender AO bake into `<name>_ao.png`. |
|
||||
| `tools/npc_proportions.py` | Exported-GLB proportion audit (no Blender). |
|
||||
| `tools/npc_baseline.py` | Contact sheets/comparisons (no Blender). |
|
||||
| `assets/models/blender/manifest.json` | Generated asset manifest. |
|
||||
| `assets/models/blender/**/*.glb` | Committed library assets. |
|
||||
| `assets/models/generated/npc_base.glb` | Frozen NPC fallback. |
|
||||
| `assets/textures/npc/*_{albedo,normal,orm}.png` | Committed NPC maps. |
|
||||
| `src/test/capture_npc_baseline.gd` | 48-frame neutral baseline. |
|
||||
| `src/test/capture_npc_review.gd` | 96-frame final-context review. |
|
||||
|
||||
## Provenance and source rules
|
||||
|
||||
- Procedural Blender assets are project-owned.
|
||||
- External/CC assets need ADR-0015 clearance: CC0 preferred, CC-BY only with
|
||||
attribution in `CREDITS.md`; no service assets with redistribution limits.
|
||||
- Editable `.blend` files under `assets/models/blender_src/` are gitignored;
|
||||
only exported `.glb`, AO maps, manifests and committed preview sheets ship.
|
||||
- AI-generated source images are development inputs, not shipped assets. Keep
|
||||
raw generations under gitignored paths until picked, checked and credited.
|
||||
- Do not copy a downloaded/Street View/AI reference into a committed asset
|
||||
without the licence and provenance review.
|
||||
|
||||
## Verification invariants
|
||||
|
||||
The asset pass is not complete unless:
|
||||
|
||||
1. `python3 tools/blender_assets.py audit` exits 0.
|
||||
2. `python3 tools/blender_assets.py fit` exits 0 and the report is reviewed.
|
||||
3. `python3 tools/npc_proportions.py report` passes the stylized guide.
|
||||
4. `python3 tools/npc_pipeline.py audit` passes when the base/textures changed.
|
||||
5. The 48-frame baseline and 96-frame final-context review are captured, with no
|
||||
blank frames.
|
||||
6. The 24-NPC budget smoke is inside its frame budget.
|
||||
7. The full Godot suite and Python tooling tests pass.
|
||||
8. No runtime contract, renderer or dependency changed except the rebuilt
|
||||
assets and their generated metadata.
|
||||
@@ -0,0 +1,130 @@
|
||||
# Verification Matrix
|
||||
|
||||
Run from the repository root. Stop and fix the first failure before continuing.
|
||||
|
||||
## Environment
|
||||
|
||||
```bash
|
||||
godot --version
|
||||
flatpak run org.blender.Blender --version | head -3
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: Godot 4.7.2; Blender 5.2 LTS flatpak; clean or intentionally scoped
|
||||
working tree. Blender's flatpak cwd is `/app/blender`; all paths passed to
|
||||
Blender must be absolute.
|
||||
|
||||
## Asset checks
|
||||
|
||||
| Check | Command | Expected |
|
||||
| --- | --- | --- |
|
||||
| Contract audit | `python3 tools/blender_assets.py audit` | exit 0, no `BAD` assets |
|
||||
| Gear fit | `python3 tools/blender_assets.py fit` | exit 0; review head band/clothing/back clearances |
|
||||
| Texture/texel density | `python3 tools/blender_assets.py texels` | authored 40–400 px/m; fabric ≥250 px/m |
|
||||
| NPC base | `python3 tools/npc_pipeline.py audit` | exit 0, contract bones/slots/maps |
|
||||
| Proportions | `python3 tools/npc_proportions.py report` | all eight inside the stylized guide |
|
||||
| Single incoming GLB | `flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- inspect --file "$PWD/path.glb" --category <cat> --json` | contract result and measurements |
|
||||
| Part GLB (local) | `flatpak run org.blender.Blender --background --python "$PWD/tools/npc_parts.py" -- inspect --file "$PWD/tmp/npc-parts/<variant>/<part>.glb"` | measurements; a part GLB is not a shipped asset |
|
||||
| Prop audit | `python3 tools/model_pipeline.py audit` | exit 0 when generated props changed |
|
||||
| Texture audit | `python3 tools/asset_pipeline.py audit assets/textures/ai/game_ready` | exit 0 when textures changed |
|
||||
|
||||
## Build/bake
|
||||
|
||||
```bash
|
||||
# one library asset
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/blender_assets.py" -- build --only npc_guard
|
||||
|
||||
# bake AO for the same asset
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/bake_ao.py" -- --only npc_guard
|
||||
|
||||
# NPC base material maps/base
|
||||
python3 tools/npc_pipeline.py prepare
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/npc_pipeline.py" -- build
|
||||
python3 tools/npc_pipeline.py audit
|
||||
```
|
||||
|
||||
Only run `prepare` when AI fabric inputs changed. Do not run a full library
|
||||
rebuild just to hide a small diff; build the affected asset and its AO map.
|
||||
|
||||
## Import and tests
|
||||
|
||||
```bash
|
||||
godot --headless --path . --import
|
||||
godot --headless --path . --script res://src/test/test_runner.gd
|
||||
python3 -m unittest discover -s tools/tests -t tools -p 'test_*.py'
|
||||
python3 tools/check_docs.py
|
||||
```
|
||||
|
||||
Expected: Godot suite passes; Python tooling suite passes; docs check OK. The
|
||||
project uses its own headless runner, not GUT.
|
||||
|
||||
## Capture gates
|
||||
|
||||
```bash
|
||||
# neutral studio baseline: 48 frames (8 characters x 6 views)
|
||||
godot --path . --script res://src/test/capture_npc_baseline.gd \
|
||||
-- --out=res://tmp/npc-baseline-after
|
||||
|
||||
# final-context: 96 frames (8 models x 3 distances x 2 venues x day/night)
|
||||
godot --path . --script res://src/test/capture_npc_review.gd \
|
||||
-- --out=res://tmp/npc-review-after
|
||||
|
||||
# optional hand close-up: 8 frames
|
||||
godot --path . --script res://src/test/capture_npc_baseline.gd \
|
||||
-- --out=res://tmp/npc-hands --view=hand
|
||||
```
|
||||
|
||||
Raw frames stay under `tmp/` and are never committed. Compare with:
|
||||
|
||||
```bash
|
||||
python3 tools/npc_baseline.py compare \
|
||||
--before tmp/npc-baseline --after tmp/npc-baseline-after \
|
||||
--out docs/blender_previews/sheet_npc_baseline_compare-<topic>.jpg
|
||||
```
|
||||
|
||||
Do not replace the committed baseline sheet until the change is accepted.
|
||||
|
||||
## Budget/performance
|
||||
|
||||
```bash
|
||||
godot --headless --path . --script res://src/test/smoke_npc_budget.gd
|
||||
```
|
||||
|
||||
Expected report: 24 avatars / 168 meshes / 131,616 triangles (current baseline)
|
||||
inside the 50 ms budget. Record the actual frame time; do not copy an old number
|
||||
without measuring.
|
||||
|
||||
## Visual review criteria
|
||||
|
||||
The part pipeline (`tools/npc_parts.py`) builds/extracts head/face/hand parts under
|
||||
`tmp/npc-parts/<variant>/`; these are local working files and are **not shipped**. Only the
|
||||
assembled character GLBs, AO/maps, the manifest and the review sheets are committed. An
|
||||
assembled GLB still goes through `blender_assets.py inspect`/`audit`/`fit` before any part is
|
||||
accepted. The `npc_base.blend` fallback is frozen: never save prototype detail into it.
|
||||
|
||||
For each changed variant, inspect at least:
|
||||
|
||||
- 2 m portrait: eyes, mouth, ears, neck/shoulder, hair/headgear clearance.
|
||||
- 4.2 m conversation: hands, gear contact, clothing silhouette.
|
||||
- 8 m crowd: readable silhouette and LOD/impostor swap.
|
||||
- Day and night/LATE final-context frames.
|
||||
- Front, three-quarter and profile for a shape change.
|
||||
|
||||
Accepted stylized limits are in `docs/npc-visual-review.md` and
|
||||
`docs/known-limitations.md`. Do not turn an accepted limit into an unreviewed
|
||||
contract change.
|
||||
|
||||
## Final diff review
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
git status --short
|
||||
git diff --stat
|
||||
```
|
||||
|
||||
Check that only intended `.glb`, AO/maps, manifest, tests and docs changed. If a
|
||||
`.blend`, raw capture, generated AI input or temporary sheet is staged, stop and
|
||||
remove it. Commit one coherent asset family per commit.
|
||||
@@ -0,0 +1,254 @@
|
||||
---
|
||||
name: gig-line-release-qa
|
||||
description: Use when preparing, validating, tagging or publishing a Gig Line release or release candidate, or when running the full screenshot, screen-design and realism QA gates. Covers version/changelog discipline, smoke and tooling matrices, clean-worktree builds, checksums, Web/HQ verification, publish rollback and post-release records. Do not use for ordinary asset edits or feature work.
|
||||
compatibility: Gig Line repository checkout with Godot 4.7.2 on PATH and Python 3 + Pillow. Release export needs installed Godot export templates; publishing needs Gitea credentials and is intentionally CI/tag-driven.
|
||||
metadata:
|
||||
project: Gig Line
|
||||
domain: release-qa
|
||||
version: "1.0.0"
|
||||
triggers: release, tag, version bump, changelog, build releases, publish, screenshots, screen design, realism judge, release candidate, deployment
|
||||
---
|
||||
|
||||
# Gig Line Release and Visual QA
|
||||
|
||||
Use this skill for version bumps, release candidates, tags, artifact builds,
|
||||
publishing, and the full visual/QA gate. For a Blender/asset rebuild, use
|
||||
`gig-line-blender-asset-contract` first.
|
||||
|
||||
## Read first
|
||||
|
||||
1. `AGENTS.md` — release procedure and verification rules.
|
||||
2. `TODO.md` — current milestone, blockers and release exit criteria.
|
||||
3. `docs/RELEASING.md` — build/publish commands, artifact list, macOS policy.
|
||||
4. `docs/release-qa.md` — manual QA matrix and balance protocol.
|
||||
5. `docs/development-status.md`, `CHANGELOG.md`, `README.md` — current claims.
|
||||
6. `MEMORY.md` — release gotchas, especially the clean-worktree rule.
|
||||
|
||||
A release is not just a tag. It includes a version bump, changelog, clean build,
|
||||
artifact/checksum verification, CI publication, and post-release records.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- **Do not tag while the TODO still lists unresolved release blockers.** Check
|
||||
external playtest, legal/AI provenance, signing and target-hardware items.
|
||||
- **Never reuse a tag.** If a published version needs a fix, bump the patch and
|
||||
create a new tag.
|
||||
- **Build from a clean checkout or detached worktree.** A normal working tree can
|
||||
pack ignored `tmp/` and `build/` content into exports, inflating a ~160 MB
|
||||
package to 1.6–3.1 GB.
|
||||
- A tag push triggers CI checks/build/publish. Do not upload manually unless CI is
|
||||
unavailable and you have followed the same preflight.
|
||||
- Do not commit generated realism runs, raw captures, API keys, or build artifacts.
|
||||
- The vision judge is an optional development aid with external API cost, never a
|
||||
hard CI gate.
|
||||
- Keep `project.godot`, changelog, tag and release asset version equal.
|
||||
|
||||
## Release workflow
|
||||
|
||||
### 1. Scope and version
|
||||
|
||||
Choose the semantic increment from `AGENTS.md`:
|
||||
|
||||
- MAJOR: incompatible/removed behavior, save/config contract or core-pillar
|
||||
replacement.
|
||||
- MINOR: new user-facing feature/venue.
|
||||
- PATCH: fixes and polish.
|
||||
|
||||
Update:
|
||||
|
||||
1. `project.godot` `config/version`.
|
||||
2. `CHANGELOG.md` with a dated entry.
|
||||
3. `README.md`, `PLAN.md`, `docs/architecture.md`,
|
||||
`docs/development-status.md`, `TODO.md`/`DONE.md` and `MEMORY.md` as needed.
|
||||
|
||||
For a release candidate, use a prerelease version only if the project policy
|
||||
allows it and never present it as the final release.
|
||||
|
||||
### 2. Preflight
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git log --oneline -5
|
||||
git tag -l "v<version>"
|
||||
rg -n 'config/version' project.godot
|
||||
```
|
||||
|
||||
Expected: only intended release changes; tag does not exist; version matches the
|
||||
changelog and planned artifacts.
|
||||
|
||||
### 3. Automated checks
|
||||
|
||||
```bash
|
||||
godot --headless --path . --script res://src/test/test_runner.gd
|
||||
python3 -m unittest discover -s tools/tests -t tools -p 'test_*.py'
|
||||
python3 tools/check_docs.py
|
||||
python3 tools/blender_assets.py audit
|
||||
python3 tools/blender_assets.py fit
|
||||
python3 tools/npc_proportions.py report
|
||||
python3 tools/npc_pipeline.py audit
|
||||
```
|
||||
|
||||
Then run the smoke matrix, using isolated user-data directories for
|
||||
tutorial/campaign:
|
||||
|
||||
```bash
|
||||
for s in smoke_audio smoke_crowd smoke_emergency smoke_npc_budget \
|
||||
smoke_plan_verbs smoke_scenario_content smoke_session_shell \
|
||||
smoke_timeline smoke_ui_flows; do
|
||||
godot --headless --path . --script res://src/test/$s.gd || exit 1
|
||||
done
|
||||
|
||||
XDG_DATA_HOME=/tmp/gig-line-campaign godot --path . --fixed-fps 60 \
|
||||
--script res://src/test/smoke_campaign.gd
|
||||
XDG_DATA_HOME=/tmp/gig-line-tutorial godot --path . --fixed-fps 60 \
|
||||
--script res://src/test/smoke_tutorial.gd -- --tutorial
|
||||
|
||||
godot --path . --script res://src/test/smoke_movement.gd
|
||||
godot --path . --script res://src/test/smoke_props.gd
|
||||
godot --path . --script res://src/test/smoke_focus_radial.gd
|
||||
```
|
||||
|
||||
Use a fresh `$XDG_DATA_HOME` for campaign/tutorial and check the exit code, not
|
||||
just the log.
|
||||
|
||||
### 4. Visual and judge gates
|
||||
|
||||
```bash
|
||||
./tools/check_screenshots.sh
|
||||
python3 tools/screen_designs.py compare
|
||||
python3 tools/screen_designs.py judge --model auto # optional, record model/verdicts
|
||||
```
|
||||
|
||||
For realism/venue work:
|
||||
|
||||
```bash
|
||||
godot --path . --script res://src/test/capture_realism.gd \
|
||||
-- --out=res://docs/screens/realism/<run>/after
|
||||
python3 tools/scene_concepts.py realism --run docs/screens/realism/<run> \
|
||||
--judge --model <pinned-model>
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- Gallery: 21 good / 0 bad; screen judge has no unexplained `diff`.
|
||||
- Realism runs stay under `docs/screens/realism/` (gitignored); only decisions and
|
||||
committed sheets/docs are tracked.
|
||||
- Record judge model, run count/attempts, spend if exposed, and verdicts.
|
||||
- Review every `review` verdict; turn layout regressions into fixes or documented
|
||||
known limitations.
|
||||
|
||||
### 5. Clean build
|
||||
|
||||
Commit the release preparation first, then build the committed tree in a clean
|
||||
worktree:
|
||||
|
||||
```bash
|
||||
git worktree add --detach /tmp/gig-line-release HEAD
|
||||
cd /tmp/gig-line-release
|
||||
./tools/build_releases.sh
|
||||
```
|
||||
|
||||
Verify artifact names/sizes and checksums:
|
||||
|
||||
```bash
|
||||
ls -lh build/gig-line-*<version>*
|
||||
PUBLISH_DRY_RUN=1 ./tools/publish_release.sh <version>
|
||||
sha256sum -c build/SHA256SUMS-<version>.txt
|
||||
```
|
||||
|
||||
Check package contents:
|
||||
|
||||
```bash
|
||||
unzip -l build/gig-line-web-<version>.zip | head
|
||||
unzip -l build/gig-line-desktop-hq-<version>.zip | head
|
||||
unzip -p build/gig-line-desktop-hq-<version>.zip override.cfg
|
||||
```
|
||||
|
||||
Check the runnable Linux and HQ packages:
|
||||
|
||||
```bash
|
||||
./build/gig-line-linux-<version>.x86_64 --headless --quit-after 60
|
||||
unzip -q build/gig-line-desktop-hq-<version>.zip -d /tmp/gig-line-hq
|
||||
/tmp/gig-line-hq/gig-line-linux-<version>.x86_64 --headless --quit-after 60
|
||||
```
|
||||
|
||||
Check Web static serving with a local HTTP server and `curl` for `index.html`,
|
||||
`index.wasm`, `index.pck` and the player docs. Windows/macOS/AppImage/installer
|
||||
checks require the target matrix; if unavailable, record that explicitly.
|
||||
|
||||
Remove the worktree after verification:
|
||||
|
||||
```bash
|
||||
git worktree remove --force /tmp/gig-line-release
|
||||
```
|
||||
|
||||
### 6. Tag and publish
|
||||
|
||||
Only after every gate is green:
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "Release <version> <topic>"
|
||||
git tag -a "v<version>" -m "Gig Line <version>"
|
||||
git push origin main "v<version>"
|
||||
```
|
||||
|
||||
CI builds and publishes on the tag. Verify:
|
||||
|
||||
- Tag commit equals the release commit.
|
||||
- CI checks/build job succeeded.
|
||||
- Release page has the expected 7 assets (Linux, Windows, macOS, NSIS setup,
|
||||
AppImage, Web, Desktop HQ).
|
||||
- Asset names and download links point at the released version.
|
||||
|
||||
`tools/publish_release.sh` preflights artifact size/free space, writes SHA-256
|
||||
checksums, rolls back newly uploaded assets/release on failure, and supports
|
||||
opt-in retention:
|
||||
|
||||
```bash
|
||||
RETAIN_RELEASES=7 ./tools/publish_release.sh <version>
|
||||
```
|
||||
|
||||
The default `RETAIN_RELEASES=0` never prunes.
|
||||
|
||||
### 7. Post-release record
|
||||
|
||||
Update `README.md`, `docs/development-status.md`, `MEMORY.md`, `CHANGELOG.md`,
|
||||
`TODO.md`/`DONE.md` and the release page with the built/CI result. Commit the
|
||||
release record and push. Record any skipped check and the reason; never leave a
|
||||
tag with unreported missing verification.
|
||||
|
||||
## Known gotchas
|
||||
|
||||
- **Dirty build inflation:** always use the clean worktree above.
|
||||
- **`Class ... hides a global script class`:** remove stray `.gd` backups in
|
||||
`tmp/`, delete `.godot`, re-import.
|
||||
- **Screenshot staging:** `capture_ui_states.gd` needs its settle frames and
|
||||
staged state; do not capture a paused frame or a stale modal.
|
||||
- **Judge keys:** OpenRouter calls need `.env`/`OPENROUTER_API_KEY`; never commit
|
||||
the key or generated review folders.
|
||||
- **Tag builds publish automatically:** do not run a second manual publish that
|
||||
duplicates assets; use the existing release if re-running.
|
||||
- **macOS is unsigned unless policy changes:** record quarantine instructions and
|
||||
do not claim notarization.
|
||||
- **Release blockers are real:** external playtest, legal/storefront approval,
|
||||
AI-provider rights and target-hardware checks cannot be replaced by green unit
|
||||
tests.
|
||||
|
||||
## Bundled references
|
||||
|
||||
- [`references/release-runbook.md`](references/release-runbook.md) — version,
|
||||
build, tag, CI, publish and rollback sequence.
|
||||
- [`references/qa-gates.md`](references/qa-gates.md) — full command matrix,
|
||||
expected results and evidence to record.
|
||||
|
||||
## Completion checklist
|
||||
|
||||
- [ ] Version/changelog/docs/tag match.
|
||||
- [ ] No unresolved release blockers for a final tag.
|
||||
- [ ] Godot, Python, smokes, asset and docs gates pass.
|
||||
- [ ] Gallery and optional judges reviewed with evidence.
|
||||
- [ ] Clean worktree build and checksums verified.
|
||||
- [ ] Linux/HQ/Web checks recorded; unavailable platforms noted.
|
||||
- [ ] Tag pushed; CI/publish succeeded; seven assets verified.
|
||||
- [ ] Release record and durable docs committed.
|
||||
@@ -0,0 +1,171 @@
|
||||
# QA Gate Matrix
|
||||
|
||||
Run from the repository root. Capture the exit code and the useful report line,
|
||||
not only the final “OK”.
|
||||
|
||||
## 1. Source and docs
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git log --oneline -5
|
||||
python3 tools/check_docs.py
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Expected: intended files only; `check_docs: OK`; no whitespace errors.
|
||||
|
||||
## 2. Automated suites
|
||||
|
||||
```bash
|
||||
godot --headless --path . --script res://src/test/test_runner.gd
|
||||
python3 -m unittest discover -s tools/tests -t tools -p 'test_*.py'
|
||||
```
|
||||
|
||||
Expected: the current Godot suite passes (historically 272+ tests) and the
|
||||
Python tooling suite passes (currently 160 tests). Do not edit expected counts
|
||||
without inspecting the suite.
|
||||
|
||||
## 3. Asset gates
|
||||
|
||||
```bash
|
||||
python3 tools/blender_assets.py audit
|
||||
python3 tools/blender_assets.py fit
|
||||
python3 tools/blender_assets.py texels
|
||||
python3 tools/npc_pipeline.py audit
|
||||
python3 tools/npc_proportions.py report
|
||||
```
|
||||
|
||||
Expected: exit 0, no `BAD` assets, all eight characters inside the stylized
|
||||
proportion guide, no budget/contract regression.
|
||||
|
||||
Optional texture/model audits when those assets changed:
|
||||
|
||||
```bash
|
||||
python3 tools/asset_pipeline.py audit assets/textures/ai/game_ready
|
||||
python3 tools/model_pipeline.py audit
|
||||
```
|
||||
|
||||
## 4. Smoke matrix
|
||||
|
||||
Headless:
|
||||
|
||||
```bash
|
||||
for s in smoke_audio smoke_crowd smoke_emergency smoke_npc_budget \
|
||||
smoke_plan_verbs smoke_scenario_content smoke_session_shell \
|
||||
smoke_timeline smoke_ui_flows; do
|
||||
echo "== $s"
|
||||
godot --headless --path . --script res://src/test/$s.gd || exit 1
|
||||
done
|
||||
```
|
||||
|
||||
Isolated data:
|
||||
|
||||
```bash
|
||||
rm -rf /tmp/gig-line-release-campaign /tmp/gig-line-release-tutorial
|
||||
XDG_DATA_HOME=/tmp/gig-line-release-campaign godot --path . --fixed-fps 60 \
|
||||
--script res://src/test/smoke_campaign.gd
|
||||
XDG_DATA_HOME=/tmp/gig-line-release-tutorial godot --path . --fixed-fps 60 \
|
||||
--script res://src/test/smoke_tutorial.gd -- --tutorial
|
||||
```
|
||||
|
||||
Windowed input/UI:
|
||||
|
||||
```bash
|
||||
godot --path . --script res://src/test/smoke_movement.gd
|
||||
godot --path . --script res://src/test/smoke_props.gd
|
||||
godot --path . --script res://src/test/smoke_focus_radial.gd
|
||||
```
|
||||
|
||||
Expected: 14 smokes green. `smoke_npc_budget` should print the current mesh,
|
||||
triangle and frame-time evidence; the 50 ms budget is a safety ceiling.
|
||||
|
||||
## 5. Screenshot and design gates
|
||||
|
||||
```bash
|
||||
./tools/check_screenshots.sh
|
||||
python3 tools/screen_designs.py compare
|
||||
```
|
||||
|
||||
Expected: 21 good / 0 bad; no unexplained compare output. The judge is optional:
|
||||
|
||||
```bash
|
||||
python3 tools/screen_designs.py judge --model auto
|
||||
```
|
||||
|
||||
Record model, confidence/verdict summary, `diff` count and any manual review.
|
||||
`docs/ui-copy.md#screen-design-judge-report` is the durable record.
|
||||
|
||||
## 6. Realism judge (when visual assets/lighting changed)
|
||||
|
||||
```bash
|
||||
godot --path . --script res://src/test/capture_realism.gd \
|
||||
-- --out=res://docs/screens/realism/<run>/after
|
||||
python3 tools/scene_concepts.py realism --run docs/screens/realism/<run> \
|
||||
--judge --model <pinned-model>
|
||||
```
|
||||
|
||||
Expected: a complete set of required stage captures, no stale pauses/modals, and
|
||||
a report under the gitignored run directory. Record:
|
||||
|
||||
- pinned model/fallbacks
|
||||
- shots attempted/completed
|
||||
- spend if the tool exposes it
|
||||
- mean score and Pillow gap
|
||||
- every `review` verdict and disposition
|
||||
|
||||
Do not commit the run. Commit only the decision/report summary and any durable
|
||||
comparison sheet explicitly intended for review.
|
||||
|
||||
## 7. Package checks
|
||||
|
||||
Use the clean worktree from `release-runbook.md`.
|
||||
|
||||
```bash
|
||||
PUBLISH_DRY_RUN=1 ./tools/publish_release.sh <version>
|
||||
sha256sum -c build/SHA256SUMS-<version>.txt
|
||||
unzip -l build/gig-line-web-<version>.zip
|
||||
unzip -l build/gig-line-desktop-hq-<version>.zip
|
||||
unzip -p build/gig-line-desktop-hq-<version>.zip override.cfg
|
||||
./build/gig-line-linux-<version>.x86_64 --headless --quit-after 60
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- names match the version;
|
||||
- Linux/Windows/macOS/Web/HQ present locally;
|
||||
- Web contains HTML/wasm/pck and docs;
|
||||
- HQ contains `override.cfg`, executables and docs;
|
||||
- Linux/HQ start without a script error.
|
||||
|
||||
Windows installer/AppImage and macOS launch require CI or the target matrix. If
|
||||
not available, mark them as unverified rather than passing them by analogy.
|
||||
|
||||
## 8. Release verification
|
||||
|
||||
After the tag push:
|
||||
|
||||
- CI checks job succeeded;
|
||||
- CI build job succeeded;
|
||||
- release page has seven assets;
|
||||
- each artifact name includes the released version;
|
||||
- the tag points at the release commit;
|
||||
- README/changelog/status and release notes agree.
|
||||
|
||||
## Evidence summary template
|
||||
|
||||
```text
|
||||
Release: vX.Y.Z (commit)
|
||||
Godot: pass (N/N)
|
||||
Python tooling: pass (N/N)
|
||||
Smokes: 14/14
|
||||
Assets: audit/fit/proportions/npc audit pass
|
||||
Budget: 24 NPCs, N meshes, N triangles, N ms
|
||||
Gallery: 21 good / 0 bad
|
||||
Screen judge: N/N match, N review, N diff
|
||||
Realism: model, attempts, mean, gap, review dispositions
|
||||
Build: clean worktree, artifact sizes/checksums
|
||||
Launch: Linux/HQ/Web checks; unverified platforms
|
||||
CI/release: run id, seven assets
|
||||
Skipped checks + reason:
|
||||
Known limitations:
|
||||
```
|
||||
@@ -0,0 +1,171 @@
|
||||
# Release Runbook
|
||||
|
||||
Authoritative source: `AGENTS.md` (Release Procedure), `docs/RELEASING.md`.
|
||||
This runbook is the operational checklist; if it conflicts with those, they win.
|
||||
|
||||
## 0. Decide the release
|
||||
|
||||
Determine whether this is a major, minor, patch, or release-candidate checkpoint.
|
||||
Do not assign a version to an unversioned development track until its scope is
|
||||
accepted into a release.
|
||||
|
||||
Check the current release constraints in `TODO.md`:
|
||||
|
||||
- external playtest evidence
|
||||
- legal/AI-provider/storefront review
|
||||
- macOS signing/notarization policy
|
||||
- target hardware matrix
|
||||
- any manual QA gaps
|
||||
|
||||
If a final release would ship with unresolved blockers, prepare an RC or stop;
|
||||
do not create a final tag.
|
||||
|
||||
## 1. Prepare the version
|
||||
|
||||
Files to update:
|
||||
|
||||
| File | Change |
|
||||
| --- | --- |
|
||||
| `project.godot` | `config/version="X.Y.Z"` |
|
||||
| `CHANGELOG.md` | dated `## [X.Y.Z] — YYYY-MM-DD` entry |
|
||||
| `README.md` | latest/current version and summary |
|
||||
| `PLAN.md` / `docs/architecture.md` | current version reference |
|
||||
| `docs/development-status.md` | shipped capability/release result |
|
||||
| `TODO.md` / `DONE.md` | close/move milestone when complete |
|
||||
| `MEMORY.md` | release state and gotchas |
|
||||
|
||||
Then commit the preparation before building.
|
||||
|
||||
## 2. Run gates
|
||||
|
||||
Use `qa-gates.md` as the command matrix. The minimum release gate is:
|
||||
|
||||
- Godot suite
|
||||
- Python tooling tests
|
||||
- documentation gate
|
||||
- asset audits/fit/proportions
|
||||
- 24-NPC budget
|
||||
- 14 smoke scripts
|
||||
- screenshot compare
|
||||
- optional screen/realism judge with recorded model/attempts
|
||||
- clean build and artifact checks
|
||||
|
||||
Any skipped check must be written into `MEMORY.md` or `TODO.md` with a reason.
|
||||
|
||||
## 3. Build in a clean worktree
|
||||
|
||||
Why: Godot can pack ignored `tmp/` captures and `build/` outputs into the export.
|
||||
A dirty build can produce 1.6–3.1 GB artifacts instead of ~160–240 MB.
|
||||
|
||||
```bash
|
||||
git worktree add --detach /tmp/gig-line-release HEAD
|
||||
cd /tmp/gig-line-release
|
||||
./tools/build_releases.sh
|
||||
```
|
||||
|
||||
Expected local artifacts:
|
||||
|
||||
```text
|
||||
build/gig-line-linux-<version>.x86_64
|
||||
build/gig-line-windows-<version>.exe
|
||||
build/gig-line-macos-<version>.zip
|
||||
build/gig-line-web-<version>.zip
|
||||
build/gig-line-desktop-hq-<version>.zip
|
||||
```
|
||||
|
||||
CI additionally produces the NSIS setup and AppImage, for seven release assets.
|
||||
|
||||
## 4. Preflight and checksum
|
||||
|
||||
```bash
|
||||
PUBLISH_DRY_RUN=1 ./tools/publish_release.sh <version>
|
||||
sha256sum -c build/SHA256SUMS-<version>.txt
|
||||
```
|
||||
|
||||
`publish_release.sh` checks non-empty artifacts and local free space before any
|
||||
API call. It writes checksums under `build/` and does not upload them by default.
|
||||
|
||||
Inspect packages:
|
||||
|
||||
```bash
|
||||
unzip -l build/gig-line-web-<version>.zip | head -20
|
||||
unzip -l build/gig-line-desktop-hq-<version>.zip | head -20
|
||||
unzip -p build/gig-line-desktop-hq-<version>.zip override.cfg
|
||||
```
|
||||
|
||||
Web must contain `index.html`, `index.wasm`, `index.pck`, and player docs.
|
||||
Desktop HQ must contain the Linux/Windows executables, `override.cfg`,
|
||||
`README.txt`, `CREDITS.md`, `PRIVACY.md`, `accessibility.md` and
|
||||
`known-limitations.md`.
|
||||
|
||||
## 5. Runtime launch smoke
|
||||
|
||||
```bash
|
||||
./build/gig-line-linux-<version>.x86_64 --headless --quit-after 60
|
||||
unzip -q build/gig-line-desktop-hq-<version>.zip -d /tmp/gig-line-hq
|
||||
/tmp/gig-line-hq/gig-line-linux-<version>.x86_64 --headless --quit-after 60
|
||||
```
|
||||
|
||||
For Web, serve `build/web` and check HTTP 200 for `index.html`, `index.wasm`,
|
||||
`index.pck` and docs. Do not claim browser interaction from a curl check.
|
||||
|
||||
Remove the temporary worktree after checks:
|
||||
|
||||
```bash
|
||||
git worktree remove --force /tmp/gig-line-release
|
||||
```
|
||||
|
||||
## 6. Tag and CI
|
||||
|
||||
From the real repository, on the release commit:
|
||||
|
||||
```bash
|
||||
git tag -a "v<version>" -m "Gig Line <version>"
|
||||
git push origin main "v<version>"
|
||||
```
|
||||
|
||||
The tag triggers `.gitea/workflows/build.yml`: checks, export, installer,
|
||||
AppImage, release publish. Verify the run and assets:
|
||||
|
||||
- check job success
|
||||
- build job success
|
||||
- seven assets present
|
||||
- tag/version/artifact names equal
|
||||
- download links respond
|
||||
|
||||
Do not reuse a published tag. If a fix is needed, bump patch and repeat.
|
||||
|
||||
## 7. Manual publish fallback
|
||||
|
||||
Only if CI cannot publish:
|
||||
|
||||
```bash
|
||||
RETAIN_RELEASES=7 ./tools/publish_release.sh <version>
|
||||
```
|
||||
|
||||
The script reuses or creates the release, uploads present artifacts, verifies the
|
||||
asset count, and rolls back newly uploaded assets/new release on failure. The
|
||||
default retention is `0` (no deletion). Never delete older releases implicitly.
|
||||
|
||||
## 8. Post-release record
|
||||
|
||||
Commit a release record that includes:
|
||||
|
||||
- tag and commit
|
||||
- CI run/result
|
||||
- seven assets
|
||||
- checksums or size evidence
|
||||
- skipped checks
|
||||
- known limitations
|
||||
- release URL
|
||||
|
||||
Update `DONE.md`/`TODO.md` and `MEMORY.md`; keep the changelog and README
|
||||
consistent. Push the record commit.
|
||||
|
||||
## Versioning pitfalls
|
||||
|
||||
- A `vX.Y.Z` tag is immutable in practice; do not move/replace it.
|
||||
- A release candidate is not a final release. Do not label it as such in the
|
||||
README or release page if blockers remain.
|
||||
- `config/version` must match the tag and artifact filenames.
|
||||
- The clean-worktree build is a release requirement, not an optimization.
|
||||
@@ -17,12 +17,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Godot 4.3
|
||||
- name: Install Godot 4.7.2
|
||||
run: |
|
||||
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
|
||||
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)
|
||||
run: godot --headless --import
|
||||
@@ -56,18 +57,24 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Godot 4.3 and export templates
|
||||
- name: Install Godot 4.7.2 and export templates
|
||||
run: |
|
||||
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
|
||||
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 \
|
||||
https://github.com/godotengine/godot/releases/download/4.3-stable/Godot_v4.3-stable_export_templates.tpz
|
||||
mkdir -p ~/.local/share/godot/export_templates/4.3.stable
|
||||
cd /tmp && rm -rf tpl && mkdir tpl && unzip -qo templates.tpz -d tpl
|
||||
cp tpl/templates/* "$HOME/.local/share/godot/export_templates/4.3.stable/" 2>/dev/null || true
|
||||
ls "$HOME/.local/share/godot/export_templates/4.3.stable" | head -5
|
||||
https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_export_templates.tpz
|
||||
rm -rf /tmp/tpl && mkdir -p /tmp/tpl
|
||||
unzip -qo /tmp/templates.tpz -d /tmp/tpl
|
||||
mkdir -p "$TEMPLATE_DIR"
|
||||
for file in $NEEDED; do
|
||||
cp "/tmp/tpl/templates/$file" "$TEMPLATE_DIR/$file"
|
||||
done
|
||||
ls "$TEMPLATE_DIR" | head -10
|
||||
|
||||
- name: Import project
|
||||
run: godot --headless --import
|
||||
@@ -114,7 +121,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
# An explicit repo secret wins. Without one, publish_release.sh reuses
|
||||
# the local git extraheader that actions/checkout configures for the job
|
||||
# the local git extraheader that actions/checkout configures on the job
|
||||
# (the runner's own credentials), so tag builds still publish.
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
VERSION: ${{ github.ref_name }}
|
||||
|
||||
@@ -37,6 +37,7 @@ Thumbs.db
|
||||
*.new
|
||||
|
||||
# Logs and temp
|
||||
tmp/
|
||||
*.log
|
||||
user://
|
||||
# CI runner workflow logs (written into the checkout by the Gitea runner)
|
||||
@@ -57,6 +58,20 @@ assets/models/generated/npc_base_*_normal.png
|
||||
|
||||
# Local AppImage extraction
|
||||
squashfs-root/
|
||||
|
||||
# Local agent skills. The third-party Blender pack is installed per machine and
|
||||
# stays ignored; the two project-authored skills are tracked so the workflow can
|
||||
# be shared and reviewed.
|
||||
.agents/
|
||||
!.agents/
|
||||
.agents/*
|
||||
!.agents/.gdignore
|
||||
!.agents/skills/
|
||||
.agents/skills/*
|
||||
!.agents/skills/gig-line-blender-asset-contract/
|
||||
!.agents/skills/gig-line-blender-asset-contract/**
|
||||
!.agents/skills/gig-line-release-qa/
|
||||
!.agents/skills/gig-line-release-qa/**
|
||||
# Blender asset library: per-asset Cycles renders are local review output (the
|
||||
# committed review artefacts are docs/blender_previews/sheet_*.jpg), and Godot
|
||||
# extracts the GLB-embedded textures next to the models on import.
|
||||
@@ -66,10 +81,14 @@ assets/models/blender/**/*.png
|
||||
assets/models/blender_src/*.blend1
|
||||
# Editable Blender sources are developer-only; keep the .gdignore marker tracked.
|
||||
assets/models/blender_src/*.blend
|
||||
# Realism pass (tools/scene_concepts.py): the candidate pool and per-run captures
|
||||
# stay local; the validated picks are tracked under game_ready/v2/picks/ (ADR-0014),
|
||||
# and each run commits its report.{json,md} + sheet.jpg. Concept references are
|
||||
# committed in docs/screens/concepts/.
|
||||
# Realism pass (tools/scene_concepts.py): candidate pools, per-run captures and
|
||||
# generated reports stay local; validated picks are tracked under
|
||||
# game_ready/v2/picks/ (ADR-0014). Concept references are committed in
|
||||
# docs/screens/concepts/, but generated QA lives outside the repository.
|
||||
assets/textures/ai/game_ready/v2/*
|
||||
!assets/textures/ai/game_ready/v2/picks/
|
||||
docs/screens/realism/**/*.png
|
||||
docs/screens/realism/
|
||||
|
||||
## Local pi agent session data
|
||||
pi-session-*.html
|
||||
pi-session-*.jsonl
|
||||
@@ -17,12 +17,104 @@ The project should be developed in small, reviewable steps. Agents must keep pla
|
||||
- `CHANGELOG.md` — The version history of user-facing changes. Keep an entry for each patch, minor, and major version.
|
||||
- `docs/development-status.md` — The implemented-features list and what is not yet implemented. Update it whenever a feature lands, is renamed, or is removed.
|
||||
- `docs/architecture.md` — The arc42-style architecture documentation (goals, constraints, building blocks, runtime views, decisions, risks).
|
||||
- `docs/gameplay.md` — Gameplay systems, venues, and the escalation model.
|
||||
- `docs/asset-tooling.md` — Asset pipelines, model tools, and screenshot gates.
|
||||
- `docs/gameplay.md` — Gameplay systems, tutorial, venues and the escalation model.
|
||||
- `docs/venues.md` — Real-site evidence, game mapping and venue simplifications.
|
||||
- `docs/asset-tooling.md` — Asset pipelines, model tools, optional Blender MCP and screenshot gates.
|
||||
- `docs/blender-assets.md` — Blender library operation and asset roles.
|
||||
- `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/smoke-checklist.md` — The manual smoke-test checklist.
|
||||
- `docs/release-qa.md` — The manual smoke checklist and balance-acceptance protocol.
|
||||
- `docs/adr/` — Architecture Decision Records.
|
||||
|
||||
## Priority and Versioning
|
||||
|
||||
`TODO.md` is the priority source. The active order is:
|
||||
|
||||
1. unversioned development tracks;
|
||||
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.
|
||||
|
||||
### Project-authored skills (tracked)
|
||||
|
||||
Two project skills are committed under `.agents/skills/` and are guidance for
|
||||
any agent, not only Pi:
|
||||
|
||||
- `gig-line-blender-asset-contract` — the NPC/gear/prop contract, build order,
|
||||
headless Blender commands, failure playbook and verification gates.
|
||||
- `gig-line-release-qa` — the test/smoke matrix, visual judges, clean-worktree
|
||||
build, checksum/tag/publish sequence and release records.
|
||||
|
||||
They are not runtime dependencies and do not move tool implementations into
|
||||
`.agents/`. The third-party `blender-skills` pack stays local/gitignored.
|
||||
|
||||
### 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` |
|
||||
|
||||
### Project rules that override a skill
|
||||
|
||||
1. **The project contract wins.** `npc_base` mesh node, five tint slots, front on Blender −Y /
|
||||
Godot +Z, grounded feet, 24,000 triangles / 24,000 glTF vertices per character, 1,500 per
|
||||
gear, 4,000 per prop. The shipped procedural library uses the 13 required core bones at their
|
||||
rest positions; optional extra bones (fingers, face, toes) are allowed if they do not move the
|
||||
core rest positions. The one documented exception is the accepted Quaternius runtime path
|
||||
([ADR-0018](docs/adr/0018-quaternius-stage2-runtime-rig.md)): it keeps the 62-joint source rig
|
||||
at runtime while the 0.9.9 face/silhouette gate is open, and it must still supply the five tint
|
||||
slots, valid gear attach points and the same budgets. Both paths stay tested until that gate
|
||||
closes. 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. **Blender MCP is optional interactive tooling.** The headless flatpak scripts
|
||||
(`tools/blender_assets.py`, `tools/npc_pipeline.py`) remain authoritative. MCP may be used
|
||||
for local part exploration; setup and safety are in [`docs/asset-tooling.md#optional-interactive-blender-mcp`](docs/asset-tooling.md#optional-interactive-blender-mcp).
|
||||
Never add an MCP dependency to the build, CI, tests or runtime, and always re-run the CLI
|
||||
audit/fit gates on anything MCP touched.
|
||||
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-0017 (the CC0 Quaternius NPC base) supersedes the generator-first
|
||||
decision in ADR-0015, and the CC0/CC-BY rule in ADR-0015 (attribution in `CREDITS.md`) plus
|
||||
the provenance rules in `docs/npc-model-sourcing.md` still apply to any new source.
|
||||
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
|
||||
|
||||
### 1. Start by Inspecting the Repository
|
||||
@@ -44,7 +136,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`.
|
||||
|
||||
- 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. Keep the hierarchy to three
|
||||
levels: milestone → workstream → checkbox.
|
||||
- Use checkbox items for each concrete task, normally under a level-3 workstream. Direct
|
||||
checkboxes under a level-2 section are acceptable for small, single-workstream sections.
|
||||
- Keep tasks small enough to complete and review independently.
|
||||
- Add new tasks when discoveries expand the scope.
|
||||
- When starting a task, claim it for the agent workflow by changing `[ ]` to `[-]`. Keep it `[-]`
|
||||
@@ -61,29 +162,53 @@ Recommended format:
|
||||
```md
|
||||
# TODO
|
||||
|
||||
## Core Gameplay
|
||||
## Milestone X.Y.Z — goal and exit criterion
|
||||
|
||||
- [ ] Add player movement controls.
|
||||
- [-] Implement NPC approach behavior. (claimed; implementation/verification in progress)
|
||||
- [x] Define escalation levels.
|
||||
### Workstream A
|
||||
|
||||
## Documentation
|
||||
- [x] Completed task.
|
||||
- [-] Task in progress. (claimed; implementation/verification in progress)
|
||||
- [ ] Open task.
|
||||
|
||||
- [ ] Document the test procedure in `README.md`.
|
||||
### Workstream B
|
||||
|
||||
- [ ] Another task.
|
||||
|
||||
**Exit:** a verifiable statement of what "done" means for the milestone.
|
||||
|
||||
## Theme
|
||||
|
||||
- [ ] A small housekeeping task with no milestone.
|
||||
```
|
||||
|
||||
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, then versioned milestones in version order, then future/deferred work.
|
||||
|
||||
### 3. Move Completed Sections to `DONE.md`
|
||||
|
||||
When every task in a `TODO.md` section is complete:
|
||||
`TODO.md` uses nested sections. Apply these completion rules:
|
||||
|
||||
1. Verify the work and update the checkbox items to `[x]`.
|
||||
2. Move the entire completed section, including its heading and checklist, to `DONE.md`.
|
||||
3. Remove that section from `TODO.md`.
|
||||
4. Add a short completion note or date when useful.
|
||||
1. **Level-3 workstream:** verify the work, mark every checkbox `[x]`, and add a short completion
|
||||
note. Leave the completed workstream under its parent milestone; do not move an isolated
|
||||
level-3 section to `DONE.md`.
|
||||
2. **Level-2 milestone/theme:** it is complete only when its direct checkboxes and **all** of its
|
||||
level-3 workstreams are complete. Verify the whole milestone, record its capabilities in
|
||||
`docs/development-status.md` when it represents shipped functionality, and write the matching
|
||||
`CHANGELOG.md` entry.
|
||||
3. Move the **entire level-2 section**, including all level-3 headings and checklists, to
|
||||
`DONE.md`. Remove it from `TODO.md` and add a completion date/note.
|
||||
4. **Planning/overview sections** such as a gap analysis, roadmap overview, dependencies or
|
||||
deferred ideas are not completion milestones. Do not move them merely because their checkboxes
|
||||
are empty or complete; update or supersede them as the roadmap changes.
|
||||
5. Do not move partially completed milestones. Leave them in `TODO.md` until every nested task is
|
||||
complete, or split the unfinished work into a new smaller level-2 milestone first.
|
||||
|
||||
Do not move partially completed sections. Leave them in `TODO.md` until all of their tasks are complete, or split the section into smaller sections first.
|
||||
|
||||
When a completed 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).
|
||||
When a completed milestone represents shipped functionality, also record the new capability in
|
||||
`docs/development-status.md` so the implemented-features list stays accurate, and write a matching
|
||||
entry in `CHANGELOG.md` (see "Maintain `CHANGELOG.md`" below).
|
||||
|
||||
### 4. Maintain `MEMORY.md`
|
||||
|
||||
@@ -149,12 +274,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.
|
||||
|
||||
- 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.
|
||||
- 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]`.
|
||||
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 +299,42 @@ For each task:
|
||||
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`.
|
||||
6. Mark the task complete.
|
||||
7. Move its section to `DONE.md` when the entire section is complete (after recording its `CHANGELOG.md` entry).
|
||||
8. Commit the changes to the version control system with a descriptive message.
|
||||
9. If a section from `TODO.md` has been fully completed, ensure its `CHANGELOG.md` entry is in place, then create a new version or tag in the version control system to mark the milestone.
|
||||
10. Push the committed changes to the shared repository if applicable.
|
||||
11. Note the new version or tag created in the version control system in any relevant documentation or handoff summary.
|
||||
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 each coherent, verified change as it is completed. Do not wait until a whole
|
||||
milestone is finished; keep commits small and descriptive and push them when the branch is
|
||||
shareable.
|
||||
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.
|
||||
|
||||
### 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.** A development track does 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
|
||||
|
||||
- Prefer clear, reversible changes over broad rewrites.
|
||||
@@ -188,6 +343,8 @@ Avoid unrelated refactors unless they are required for the task or explicitly ap
|
||||
- Do not claim a task is complete without verification.
|
||||
- Keep documentation synchronized with the implementation.
|
||||
- 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.
|
||||
- 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.
|
||||
@@ -199,6 +356,10 @@ Before handing off work, an agent must run every check below that applies to the
|
||||
- Run automated tests.
|
||||
- Run formatting, linting, or type checks.
|
||||
- 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.
|
||||
- Review the final diff for accidental changes or unfinished markers.
|
||||
|
||||
|
||||
@@ -4,6 +4,308 @@ All notable user-facing changes to Gig Line. The format follows
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
|
||||
Semantic Versioning.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.9.9] — 2026-11-06
|
||||
|
||||
### Changed
|
||||
|
||||
- Raised the character mesh budget from 16,000 to 24,000 triangles and glTF
|
||||
vertices so the Quaternius base and the role-dressed variants fit without
|
||||
heavy decimation. Gear and prop budgets are unchanged.
|
||||
- Replaced the whole NPC roster with the CC0 Quaternius characters. All twelve
|
||||
characters (tourist, jacket, coat, vest, dress, hoodie, firefighter, guard and
|
||||
the four crowd variants) now use one of three source outfits — casual tee,
|
||||
hoodie or work clothes — dressed by role with the five tint slots and the gear
|
||||
library. The procedural library and the MakeHuman head remain in the build as
|
||||
the fallback.
|
||||
- Characters stand with their arms down instead of a T/A pose, and the seated,
|
||||
idle and walking poses swing from the shoulder and hip the way the animation
|
||||
code intends on the new skeleton.
|
||||
- Head gear (cap, hood, sunglasses, firefighter helmet) and the camera were
|
||||
resized for the new head and arm. The camera now hangs from a wrist strap
|
||||
instead of a neck strap that floated next to the head.
|
||||
- The backpack straps were pulled in so they sit on the chest instead of
|
||||
hovering in front of it.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Characters no longer render with black legs or black patches: the new base
|
||||
carried a collapsed UV map that made the ambient-occlusion bake come out
|
||||
black, and the roster is now unwrapped and baked correctly.
|
||||
|
||||
## [0.9.8] — 2026-11-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Characters no longer detach at the wrist: the wrist and hand now track the same
|
||||
bone, the hand was raised to meet the forearm, and the sleeve cuff follows the
|
||||
hand instead of the elbow.
|
||||
- Seated and walking poses no longer collapse the hip/thigh or pinch the knee:
|
||||
the thigh and shin are rigid to their bones with a short blend at the knee.
|
||||
|
||||
## [0.9.7] — 2026-11-04
|
||||
|
||||
### Added
|
||||
|
||||
- Character development: a more realistic face on all eight library characters
|
||||
(higher head subdivision, brow ridge, both eyelids, fuller lower lip) and four
|
||||
crowd-variety characters (`npc_crowd_a`–`d`) with deterministic per-role pools
|
||||
so the routine crowd no longer clones a single look.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The NPC pose harness now captures seated, walking and idle deformation (the
|
||||
harness froze the avatar's process so the FSM could no longer revert the pose).
|
||||
|
||||
## [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
|
||||
|
||||
### Added
|
||||
|
||||
- Automatic OpenRouter model selection for the screenshot judges: the cheapest
|
||||
currently available image+JSON-capable model is used, with ordered fallbacks
|
||||
and a pinned `GIG_LINE_JUDGE_MODEL`/`--model` override.
|
||||
|
||||
### Changed
|
||||
|
||||
- The judge reports the model that produced each verdict and fails clearly on an
|
||||
invalid API key instead of retrying other models.
|
||||
- The screen-design and realism reports now include the fresh judge results.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The blocked screenshot judge now runs with the valid `OPENROUTER_API_KEY`
|
||||
(`13/13` screen designs match, `0` diff).
|
||||
|
||||
## [0.7.1] — 2026-11-01
|
||||
|
||||
### Added
|
||||
@@ -30,12 +332,6 @@ Semantic Versioning.
|
||||
|
||||
## [0.7.0] — 2026-11-01
|
||||
|
||||
### Changed
|
||||
|
||||
- Agent workflow now requires maintaining this changelog for every patch, minor, and major version.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Distinct venue geometry: the Joke Shop is now a narrow brick alley with a
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# 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 local MakeHuman head prototype uses the CC0 MakeHuman/MPFB base mesh
|
||||
(see [`docs/makehuman-head-review.md`](docs/makehuman-head-review.md)); it is a
|
||||
development prototype, not a shipped character.
|
||||
|
||||
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.
|
||||
|
||||
### MakeHuman head source
|
||||
|
||||
The standalone MakeHuman head prototype is derived from the MakeHuman / MPFB base
|
||||
mesh, **CC0** (`assets/models/blender_src/makehuman_head.glb`; source
|
||||
<https://static.makehumancommunity.org/makehuman.html>, MakeHuman base mesh,
|
||||
CC0 1.0). It is cut, retopologised and re-weighted by project scripts; the exported
|
||||
character GLBs remain project assets. The development source files were removed
|
||||
from `tmp/` after the 2026-11-05 pivot to Quaternius. The head is retained as a
|
||||
tested fallback, not the active NPC base. See ADR-0017 and
|
||||
`docs/npc-model-sourcing.md`.
|
||||
|
||||
### Quaternius NPC base source
|
||||
|
||||
The active NPC base migration uses **Quaternius Ultimate Modular Characters** and
|
||||
**Ultimate Modular Women** (CC0 1.0, <https://quaternius.com/>). The three
|
||||
researched GLBs are development inputs and are not shipped directly; the baked,
|
||||
retargeted character GLBs are project assets. See ADR-0017 and
|
||||
`docs/npc-model-sourcing.md` for the decision, source hashes and rollout status.
|
||||
|
||||
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.
|
||||
|
||||
### Development-only reference images
|
||||
|
||||
The venue reference boards under `docs/screens/refs/` are development documentation and are not
|
||||
bundled with the game or its release packages. Files under CC BY are attributed here as well as in
|
||||
their folder sidecar:
|
||||
|
||||
- `docs/screens/refs/hollywood/el-capitan-facade-day-wikimedia-2009-10-27.jpg` — "woo (Irvine, CA,
|
||||
USA)", CC BY 2.0, via Wikimedia Commons.
|
||||
- `docs/screens/refs/hollywood/el-capitan-theatre-blade-sign-day-wikimedia-2008-10.jpg` — Andreas
|
||||
Praefcke, CC BY 3.0, via Wikimedia Commons.
|
||||
- `docs/screens/refs/hollywood/premiere-staging-street-day-wikimedia-2006-02-12.jpg` — John
|
||||
Barreiros, CC BY 2.0, via Wikimedia Commons.
|
||||
- Public-domain files in the same folder: Charles W. Beam (1922) and Gary Minnaert (2006).
|
||||
- `docs/screens/refs/hollywood/el-capitan-theatre-marquee-neighbour-day-wikimedia-2009-07-11.jpg` —
|
||||
Alan Light, CC BY 2.0, via Wikimedia Commons.
|
||||
- `docs/screens/refs/hollywood/hollywood-boulevard-night-context-ccbysa.jpg` — Downtowngal,
|
||||
CC BY-SA 3.0, via Wikimedia Commons; used unmodified for colour/lighting study only, no derivative
|
||||
asset is made from it.
|
||||
- CC0 files in the Hollywood folder need no attribution: Luijtenphotos (2019) and Benoît Prieur
|
||||
(2022).
|
||||
|
||||
## 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.
|
||||
@@ -1109,7 +1109,6 @@ Completed 2026-11-01. The follow-ups left over after the texture/skyline adoptio
|
||||
|
||||
Completed 2026-11-01.
|
||||
|
||||
|
||||
### Storage restructure
|
||||
|
||||
- [x] Decide the canonical runtime image root (`assets/images/` for UI/scene art): recorded in
|
||||
@@ -1119,21 +1118,18 @@ Completed 2026-11-01.
|
||||
- [x] Re-run `godot --headless --path . --import` to regenerate `.import` sidecars after any move.
|
||||
- [x] Confirm `.gitignore` still excludes development-only outputs (concepts, Blender sources, raw AI fabrics) under the new layout; Blender `.blend` sources are now untracked and ignored.
|
||||
|
||||
|
||||
### Documentation path updates
|
||||
|
||||
- [x] Update `README.md` paths (screen-designs section, confirmed-assets links, the `assets/images/scenes` resize example).
|
||||
- [x] Update `docs/architecture.md`, `PLAN.md`, and ADR references: docs/screens/designs/ is now explicitly the reference-only snapshot; runtime art lives in assets/images/ (ADR-0014).
|
||||
- [x] Update `tools/screen_designs.py` docstrings and mark the old paths in `DONE.md` as historical.
|
||||
|
||||
|
||||
### Code path updates
|
||||
|
||||
- [x] Point `tools/screen_designs.py generate` at the new design output path and keep `compare`/`judge` reading the right locations.
|
||||
- [x] Update GDScript `preload()` paths if game textures move (no runtime material/model textures moved; UI art added under assets/images/).
|
||||
- [x] Update capture output directories if captures relocate (none did; QA captures stay in docs/screenshots/).
|
||||
|
||||
|
||||
### Load proposed interfaces + scene art from disk
|
||||
|
||||
- [x] Add a central UI/scene art loader (e.g. `src/game/ui/art.gd`) that loads the mockup-derived images from the new runtime root, with a procedural fallback when a file is missing.
|
||||
@@ -1144,19 +1140,16 @@ Completed 2026-11-01.
|
||||
- [x] Wire the wand popup to `wand_screening` art.
|
||||
- [x] Style the radial menu and finish the remaining HUD chrome against their reference images.
|
||||
|
||||
|
||||
### Verify against the mockups
|
||||
|
||||
- [x] Regenerate the staged captures and run `screen_designs.py compare` (11 good, 0 bad; verified in a clean HEAD worktree).
|
||||
- [x] Run `screen_designs.py judge` and clear every `diff` verdict (7 match, 4 review, 0 diff).
|
||||
- [x] Add the capture + compare step as a repeatable smoke so drift is caught early (`tools/check_screenshots.sh`).
|
||||
|
||||
|
||||
## Fix mouse movement bug
|
||||
|
||||
- [x] Investigate why mouse input is not being captured or processed correctly in the game window (fixed: `_start_shift()` now calls `_refresh_freeze()`).
|
||||
|
||||
|
||||
## Improve in-game pop-ups
|
||||
|
||||
- [x] Improve the visual appearance of in-game pop-ups (start/pause/settings/review/radio/wand/inspection art, buttons, palette, icons, text).
|
||||
@@ -1311,3 +1304,891 @@ path.
|
||||
to Compatibility instead of failing to start). Verified with the preset in the project
|
||||
root: `godot --headless --path . --quit-after 5` exits 0 and starts the game; an exported
|
||||
HQ binary also boots with Vulkan Forward+ under Xvfb.
|
||||
|
||||
## Vision judge reliability (v0.7.2)
|
||||
|
||||
Completed 2026-11-01. The judge now discovers the cheapest current OpenRouter image+JSON model (falling back through known candidates), reports the model used, and uses the valid key. Full screen-design judge: 13/13 match, 0 diff.
|
||||
|
||||
The judge failed with an invalid-key HTTP 400 even though the `.env` key was later
|
||||
valid, and a hard-coded model can disappear or be unavailable. Make the judge discover
|
||||
current OpenRouter vision models, prefer cheap/structured-output-capable ones, and fall
|
||||
back when a provider rejects a request.
|
||||
|
||||
- [x] Add `tools/openrouter_judge.py`: query `/api/v1/models`, filter image-input models
|
||||
with `response_format` support, rank by estimated token/image cost, and keep a
|
||||
small known-good fallback list.
|
||||
- [x] Let both `screen_designs.py judge` and `scene_concepts.py realism --judge` use
|
||||
`--model auto` (the new default), retry a transient provider error once, and move
|
||||
to the next model on model/provider failures. An invalid key fails with exit 2.
|
||||
- [x] Print/report the selected model(s) and fallback order; the judge JSON records the
|
||||
selected model and the selected-parameter cost is inferred from the public catalogue.
|
||||
- [x] Add `tools/test_openrouter_judge.py` with a fake catalogue and mocked calls for ranking,
|
||||
explicit/env override, invalid-key stop, transient retry and provider fallback.
|
||||
- [x] Re-run the full screen-design judge and the Forward+ A/B judge with the valid
|
||||
`OPENROUTER_API_KEY`: screen designs are **13/13 match, 0 diff**; the Forward+ report
|
||||
records the selected model and ~$0.006 spend.
|
||||
- [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).
|
||||
|
||||
## 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/gameplay.md#first-shift-tutorial`](docs/gameplay.md#first-shift-tutorial).
|
||||
- [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/release-qa.md#manual-smoke-checklist` 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/venues.md#hollywood-boulevard`.
|
||||
|
||||
### 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/venues.md#the-joke-shop` 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/ui-copy.md#screen-design-judge-report`; the
|
||||
generated captures remain local/gitignored.
|
||||
- [x] Add a short UI copy/button checklist to `docs/release-qa.md#manual-smoke-checklist`.
|
||||
- [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-visual-review.md#proportion-audit`: 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.
|
||||
|
||||
## Character development
|
||||
|
||||
Completed 2026-11-04. The procedural crowd-variety roster (four new variants, per-role pools)
|
||||
shipped with the realistic face; the external-asset intake and the MakeHuman body pipeline were
|
||||
promoted to the active **MakeHuman character pipeline** track in `TODO.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 and local map 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.)
|
||||
- [x] `adapt` path: promoted to the **MakeHuman character pipeline** track (2026-11-04).: 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.
|
||||
- [x] Audit gate + intake regression test: promoted to the **MakeHuman character pipeline** track.
|
||||
- [x] Adapter documentation: promoted to the **MakeHuman character pipeline** track.
|
||||
|
||||
### 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.)
|
||||
- [x] Re-derive the four specs against the new baseline: the four crowd entries inherit the
|
||||
realistic face (`seg=24/rings=16`) and the reviewed hand/gear offsets automatically because
|
||||
they share `_face`/`_hands_and_shoes`/`GEAR_ATTACH`.
|
||||
- [x] Generate the four variants: built via `blender_assets.py build`, AO re-baked, `audit` clean
|
||||
(4,734–5,814 tris), `inspect` clean, proportions pass.
|
||||
- [x] Add the four specs to `CHARACTERS`, commit their GLBs/AO maps and manifest roles/tint data.
|
||||
(Note: this adds ~2 MB to the next release build via `export_filter=all_resources` — accepted
|
||||
for the crowd-variety roster.)
|
||||
- [x] Per-role pools: `character_model_for()` now picks from `CHARACTER_POOLS` by seed — the
|
||||
routine crowd roles gain the crowd variants (tourist → casual/crowd_a/crowd_d,
|
||||
confused_visitor → casual/crowd_b, intoxicated → casual/crowd_c) while the special roles
|
||||
keep their existing alternation (guest ⅓ jacket, trespasser/threat ½ hoodie). The test
|
||||
now checks seed-stability plus that every pool entry resolves to a committed GLB
|
||||
(277/277).
|
||||
- [x] Gear fit + inspect for the four new variants: `fit` is clean (sunglasses band=inside,
|
||||
shoulder bag clear; cup/umbrella are hand gear with the forward offset), `inspect` clean,
|
||||
proportions pass.
|
||||
- [x] Crowd-variety reference: five licence-cleared Commons photos fetched to
|
||||
`tmp/research/crowd-variety/`; the AI proposal set stays the next step for detail.
|
||||
- [x] Baseline + portrait rows: the four variants are in `capture_npc_baseline.gd` and
|
||||
`sheet_npc_baseline.jpg` (12 variants × 6 views); review-doc rows added.
|
||||
|
||||
### QA and release
|
||||
|
||||
- [x] Gates with the expanded library: 24-NPC budget smoke 152,464 tris / 168 meshes / 6.94 ms
|
||||
(under 50 ms), portrait baseline 12 variants × 6 views, `fit` clean, Godot 277/277, Python
|
||||
160/160. No release build is created for this unversioned track.
|
||||
- [x] Docs: `docs/development-status.md`, `docs/3d-model-review.md` and the baseline sheet updated;
|
||||
`CREDITS.md` needs no change (the crowd variants are procedural, no new source). Accepted
|
||||
visual limits: the crowd accessories (umbrella canopy, cup, sunglasses, beard) read at
|
||||
full-body distance but not in the head-only portrait framing.
|
||||
- [x] Exit: the 12-character library passes contract audit, portrait/gear acceptance, the 24-NPC
|
||||
budget and licence/provenance review; the runtime contract (13 bones, five tint slots,
|
||||
`npc_base` fallback) is unchanged.
|
||||
|
||||
### 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.)
|
||||
- [x] Body-shape diversity decision (2026-11-04, owner): **yes** — use MakeHuman for the next
|
||||
character pass. Promoted to the **MakeHuman character pipeline** track.
|
||||
|
||||
## NPC pose harness (v0.9.8)
|
||||
|
||||
Completed 2026-11-04. The pose harness bug that made every staged capture save a rest frame
|
||||
was found and fixed; it unblocked the weights/pose evidence and the seated/walking deformation
|
||||
fixes. User-facing changes are recorded under 0.9.7 and 0.9.8 in `CHANGELOG.md`.
|
||||
|
||||
Goal: `src/test/capture_npc_pose.gd` reports a pose but saves rest frames; the root cause is
|
||||
narrowed to the skinned mesh ignoring its `Rig` parent transform. Fixing it unblocks the
|
||||
weights/pose column of the 3D review; the evidence now feeds the Quaternius base bake.
|
||||
|
||||
*Skill route: `rigging`; evidence in `docs/3d-model-review.md` §"Pose evidence is blocked".*
|
||||
|
||||
- [x] Root cause found (2026-11-04): the harness never disabled the avatar's `_physics_process`,
|
||||
so `_advance_fsm` + `_move_toward_target` ran every tick and reset `arrived`/velocity, which
|
||||
made `theater_pose()` return "idle" and reverted the pose to rest. (The earlier "moving the
|
||||
`Rig` child does not render" probe was a red herring: it set `rig.position.y` inside `_shoot()`
|
||||
in the same frame as `root.get_texture().get_image()`, so it never got a rendered frame.)
|
||||
- [x] Harness rebuilt: `capture_npc_pose.gd` now calls `set_physics_process(false)`/
|
||||
`set_process(false)` after staging the avatar; the pose survives because only the direct
|
||||
`_animate()` ticks drive it. Verified: sit vs idle diff 5.63, walk vs idle diff 2.29, and the
|
||||
vision judge confirms the seated frame reads as crouched.
|
||||
- [x] Captured all poses for all 12 variants (idle / walk_contact / walk_pass / sit × full +
|
||||
shoulder views = 96 frames) and built `sheet_pose.jpg`/`sheet_shoulder.jpg` under `tmp/`.
|
||||
(No separate "wave" theater pose exists; the escalation raise-hand gesture is not a distinct
|
||||
capture in this harness.)
|
||||
|
||||
## Milestone 0.9.7 — Character development (patch)
|
||||
|
||||
Completed 2026-11-04 and published as v0.9.7. The detailed character-development record already
|
||||
lives under "Character development" above; this milestone checklist is kept verbatim for the
|
||||
release record.
|
||||
|
||||
Goal: release the finished procedural character development — the realistic face on all eight
|
||||
variants, the four crowd-variety characters and the per-role pools — as a **patch** on top of
|
||||
0.9.6. This keeps the 3D model and map improvements out of the patch; they land together in the
|
||||
0.10.0 minor.
|
||||
|
||||
- [x] Rig pose harness fixed and the weights/pose evidence captured (see the top track).
|
||||
- [x] Character work done: 12-character library, realistic face, crowd variants, per-role pools,
|
||||
24-NPC budget 6.94 ms (152,464 tris / 168 meshes).
|
||||
- [x] QA + local build for 0.9.7: Godot 277/277, Python 161/161, audit 49 OK, fit/proportions
|
||||
12/12, release smoke matrix green, gallery 21/0, clean worktree build (Linux 172 MB,
|
||||
Windows 206 MB, macOS 159 MB, Web 111 MB, HQ 266 MB), checksums verified, Linux binary
|
||||
launches headless. Tagged `v0.9.7` (CI build/publish follows).
|
||||
|
||||
**Exit:** met — 0.9.7 ships the 12-character library (face realism + crowd variety) with the pose
|
||||
harness fixed; the runtime contract is unchanged. The Quaternius NPC base and the map rebuild are
|
||||
not in this patch (they target 0.10.0).
|
||||
@@ -1,673 +1,289 @@
|
||||
# MEMORY
|
||||
|
||||
Session memory for durable decisions, state, and gotchas. Keep concise, remove obsolete entries.
|
||||
Session memory for durable decisions, gotchas, and commands. Implementation history lives in
|
||||
`DONE.md`, user-facing changes in `CHANGELOG.md`, the live task list in `TODO.md`, and the
|
||||
canonical implemented-features list in `docs/development-status.md`.
|
||||
|
||||
## Decisions
|
||||
|
||||
- The visual-alignment path/loader/panel/HUD work and the realism lighting pass are complete and
|
||||
recorded in `DONE.md`; remaining visual items are the realism follow-ups in `TODO.md`.
|
||||
- `PLAN.md` is a compact product/design document, not an implementation log. Historical control
|
||||
and v0.3.0 specs were folded into its Presentation and Controls section; put new step-by-step
|
||||
work in `TODO.md`, not the plan.
|
||||
- Editable Blender sources under `assets/models/blender_src/` are gitignored and untracked
|
||||
(46 `.blend` files removed from the index on 2026-11-01); the `.gdignore` marker stays tracked.
|
||||
- `screen_designs.py judge` default `google/gemini-2.5-flash-lite` worked on 2026-11-01
|
||||
(7 match / 4 review / 0 diff); `--model` remains available.
|
||||
- `capture_realism.gd` parses `--portrait-type=N`; it was documented but ignored before the gear
|
||||
pass. Portrait captures need the requested NPC type to have spawned, otherwise the stage can
|
||||
finish with an empty frame; use the Blender character previews (`render --only npc_coat`) for
|
||||
deterministic gear checks.
|
||||
- Headgear: the firefighter helmet was lifted 2 cm after the brow/face pass; cap and hood were
|
||||
visually checked on `npc_vest`/`npc_hoodie`. The beanie is unused and shares the cap shell.
|
||||
- Gear fit is measured with `python3 tools/blender_assets.py fit` (stdlib, reads the GLBs):
|
||||
head width uses skin vertices weighted to Head in y 1.55–1.81; clothing radius is the cloth
|
||||
skirt in y 0.30–0.52 with a pants hip fallback. The command reports per-character gear boxes,
|
||||
head-band status and x/radial skirt clearance; `test_blender_assets` covers its shape.
|
||||
- Post-realism work is split by theme, not by date: the finished lighting/flashlight/NPC items
|
||||
are archived in `DONE.md`, and `TODO.md` carries three planned workstreams (NPC gear fit,
|
||||
venue geometry + committed gallery, Forward+ desktop preset). New visual work should be added
|
||||
as concrete steps under one of those headings instead of a new "realism" bucket.
|
||||
- **Local-only reference override (2026-11-03, owner):** development-only photo research ignores
|
||||
the licence gate. Downloads live in `tmp/research/` (gitignored): DDG image search, street-view
|
||||
frames, neighbouring buildings — never committed, never shipped. Promotion into
|
||||
`docs/screens/refs/` always passes the sidecar licence + privacy gate and `CREDITS.md`.
|
||||
- **AI-assisted asset review loop (2026-11-04):** head/face shape decisions use a concept +
|
||||
judge loop — generate a target concept (`generate_textures.generate_image`, Seedream) and use
|
||||
`tools/describe_image.py` as the vision judge, iterating until the judge's specific fixes
|
||||
converge. Review pages: `docs/screens/head-review/README.md`, `docs/3d-model-review.md`
|
||||
§AI-driven head rework.
|
||||
- **Project agent skills (2026-11-03):** two tracked skills live in `.agents/skills/`:
|
||||
`gig-line-blender-asset-contract` and `gig-line-release-qa`. They are guidance only,
|
||||
add no runtime dependency and are referenced from `tools/README.md`. The third-party
|
||||
`blender-skills` pack remains gitignored.
|
||||
- **TV Studio shares the Hollywood location (owner):** the Late Night TV Studio is the same El
|
||||
Capitan Entertainment Centre block at 6840 Hollywood Blvd (which houses *Jimmy Kimmel Live!*),
|
||||
restaged for a late-night arrival. `VENUE_SPECS["tv_studio"]` is a 30.0 m variant of the 29.6 m
|
||||
Hollywood facade. One reference board serves both venues; never create a separate TV Studio board.
|
||||
- **Jimmy Kimmel Live = starting point:** the Hollywood first shift and the TV Studio arrival both
|
||||
begin at the El Capitan block. Stated in `docs/venues.md#hollywood-boulevard` and `docs/gameplay.md`.
|
||||
- **Street-view limitation:** Google Street View panoramas need a Google API key or manual browser
|
||||
capture, so the local reference sets use DDG street-level photos instead.
|
||||
- **Image-recitation filter:** Gemini image models reject well-known branded facades (Comedy Store
|
||||
wall) as generation anchors (`IMAGE_RECITATION`). Workaround: anchor on a less-branded frame of
|
||||
the same block, or generate text-only when the reference adds no geometry.
|
||||
- **OpenRouter image-to-image:** `/v1/images` is text-to-image only; the working path is
|
||||
`POST /v1/chat/completions` with a `content` array carrying a base64 `image_url`. Response
|
||||
images arrive as `message.images[].image_url.url` (fallback: a data URI inside the message text).
|
||||
Implemented in `generate_textures.generate_image(reference_path=...)` and
|
||||
`scene_concepts.py generate/anchor`; provenance sidecars are `.refgen.json`.
|
||||
- **Facade/venue material picks (2026-11-03):** the Masonic/El Capitan facade uses the
|
||||
photo-anchored `limestone_ashlar` field (warm tint `Color(0.85, 0.81, 0.74)`) with `stone_steps`
|
||||
for trim; the Joke Shop uses `dark_stucco`; the marquee canopy/sign box stay `black_metal` by
|
||||
design (fictional "GIG LINE" marquee, not the red El Capitan Theatre marquee next door). The
|
||||
TV Studio reuses `masonic_facade`. Committed Comedy Store refs were removed in favour of the
|
||||
local-only `tmp/research/comedy-store/` set; the three-up board's photo panel is a historical
|
||||
public-domain frame.
|
||||
- **Godot 4.7.2 `--import` gotcha:** with a stale `.godot/` cache, new image files never import
|
||||
(no `.import` created) and `preload()` of the new texture fails every dependent script with the
|
||||
misleading `Could not resolve external class member "spec"`. Fix: import in a scratch copy with
|
||||
`.godot/` removed, then copy back the `.png.import` and `.godot/imported/*.ctex/.md5`.
|
||||
- **Blender skills pack (local aid):** MIT `blender-skills` at `.agents/skills/blender-skills/`
|
||||
(94 skills + shared `references/`), gitignored, kept out of Godot's scanner by
|
||||
`.agents/.gdignore`. Keep the pack flat when updating (`cp -a <pack>/.claude/skills/. ...`).
|
||||
Pi gates project `.agents/skills` behind project trust.
|
||||
- **Gaussian splatting (unversioned):** offline/development-only until an ADR proves licence,
|
||||
privacy, Compatibility/Web cost and mesh collision. Accepted photo channels: team captures,
|
||||
permissioned user/volunteer photos, clearly licensed downloads; every set needs a
|
||||
provenance/consent sidecar. No runtime splat dependency in 0.10.0.
|
||||
- **0.10.0 balance protocol:** `docs/release-qa.md#balance-acceptance` defines targets/denominators for the
|
||||
36 venue/difficulty/phase cells, seeds 1–100, and access/emergency error rates. Gotcha:
|
||||
`_read_npc_truth()` is type-derived, not behaviour-derived; the emergency-fairness review must
|
||||
account for that oracle mismatch.
|
||||
- **Blender MCP is optional (2026-11-04):** setup and safe part workflow are in
|
||||
`docs/asset-tooling.md#optional-interactive-blender-mcp`. The headless flatpak scripts remain authoritative; MCP is
|
||||
never a build/CI/test/runtime dependency, and anything MCP touched must pass the
|
||||
CLI audit/fit gates.
|
||||
- **Character mesh cap (2026-11-05):** all characters use 24,000 tris / 24,000 glTF vertices
|
||||
(`CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in `tools/blender_assets.py`; `npc_pipeline.MAX_TRIANGLES`/
|
||||
`MAX_VERTICES`). The required 13 core bones stay, optional extra bones are allowed, and the
|
||||
low-poly `npc_base` fallback remains the shipped fallback. The raise funds the Quaternius base bake and
|
||||
the 12-variant clothing detail. Measure the 24-NPC/Web budget after
|
||||
any geometry change; never silently lower the cap without a new decision.
|
||||
- **Quaternius is the NPC base (2026-11-05, owner):** ADR-0017 supersedes the generator-first
|
||||
sourcing decision. The CC0 Quaternius Ultimate Modular character pack is the primary NPC mesh
|
||||
and animation source. Stage 1 bakes a proportion-warped, 13-core-contract `npc_base` so
|
||||
`NpcAvatar`, gear and animations keep working; the 62-joint/24-clip source rig is authoring
|
||||
input, not a runtime dependency. If the warp fails the silhouette gate, Stage 2 needs a new ADR
|
||||
for a 62-joint runtime, not a hybrid. The MakeHuman head and procedural `npc_base` remain
|
||||
tested fallbacks until the Quaternius rollout ships. Quaternius source: CC0, attribution in
|
||||
`CREDITS.md`; the three researched GLBs and licence notes are kept in
|
||||
`tmp/npc-research/quaternius/`. MPFB/MakeHuman body work is closed; do not add new work there.
|
||||
- **Quaternius bake keeps source detail (2026-11-05, owner):** during the contract bake (§2),
|
||||
keep the Quaternius mesh as close to source as possible — no unnecessary decimation or LOD
|
||||
reduction. The canonical base is only 3,199 verts / 6,206 tris, far under the 24,000 cap, so
|
||||
there is no budget reason to decimate before the silhouette gate. Do not reuse the old
|
||||
7,000-triangle or MPFB decimation targets.
|
||||
- **Quaternius runtime rig (2026-11-05, owner, ADR-0018 option 2):** the Stage 1 proportion warp
|
||||
failed the silhouette gate (arm ×1.63 / leg ×0.74 / torso ×1.61; vision judge: paddle hands,
|
||||
lost forearms, stretched shins). The owner chose to **adopt the Quaternius 62-joint rig at
|
||||
runtime** instead of warping: `NpcAvatar` maps the Quaternius joint names, gear attach points
|
||||
are re-derived from the Quaternius skeleton (hip ≈0.87, shoulder ≈0.148/1.39, wrist ≈0.99,
|
||||
knee ≈0.52), the mesh keeps source LOD (arm-down rest pose baked), and Idle/Walk play directly
|
||||
on the 62-joint rig (no retarget).
|
||||
- **Engine baseline:** Godot 4.7.2 stable (official editor `~/.local/bin/godot`). The system's
|
||||
`godot-runner` cannot load arbitrary project paths (compiled without path overrides); use the
|
||||
official editor binary.
|
||||
- **GDScript/architecture:** cross-file references use explicit `preload()` constants (not
|
||||
`class_name`) so scripts compile in headless `--script` runs; the test runner is a custom minimal
|
||||
harness (not GUT, which needs the editor plugin); no autoload signal bus — `ShiftCoordinator`
|
||||
owns the `EventBus` instance passed to subsystems.
|
||||
- **Skinned NPCs (v0.6.0):** `NpcAvatar._build_skinned_body()` loads `npc_base.glb`; procedural
|
||||
`_build_body()` is the fallback. Bone pose rotation is the **absolute local orientation**, not an
|
||||
offset — compose with `get_bone_rest(i).basis` or the body snaps to a broken pose. Five tint
|
||||
slots via `SurfaceMaterials.npc_slot()`; keep `_rig.scale` uniform on the skinned path.
|
||||
- **NPC facing:** the avatar's visual front is **+Z**; Godot `look_at()` aims **-Z** (made NPCs
|
||||
walk backwards). Use `NpcAvatar.yaw_towards(dir)` and set `rotation.y`; never `look_at()` on
|
||||
avatars. Head-turn offsets use `+basis.z` for the same reason.
|
||||
- **Release blockers (still open):** macOS unsigned, AI image provider terms/commercial rights not
|
||||
cleared, storefront/rating approval pending, external playtests and full hardware matrix pending.
|
||||
Recorded in `CREDITS.md`, `PRIVACY.md`, `docs/accessibility.md`, `docs/known-limitations.md`.
|
||||
|
||||
- Graphics v0.6.0 plan (2026-11-01): NPCs move to Blender-authored skinned meshes
|
||||
(`docs/plan-graphics-v0.6.md`, ADR-0013). `tools/npc_pipeline.py` builds
|
||||
`assets/models/generated/npc_base.glb`; `NpcAvatar` keeps the procedural `_build_body()`
|
||||
fallback. Preserve `Rig/Head`, the limb node names, and the seed-deterministic
|
||||
`outfit_for`/`layer_for`/`hand_item_for` helpers. Blender = flatpak 5.2.0 LTS, sandbox cwd
|
||||
`/app/blender` → absolute paths for `--python`. CI stays offline (committed assets).
|
||||
- **Release roadmap (2026-11-05, owner):** `0.9.9` Quaternius pilot base (rig/export fix,
|
||||
rest-relative pose math, owner face/silhouette gate), `0.9.10` 12-role roster, `0.9.11`
|
||||
scheduled venue/prop/lighting polish, `0.9.12` performance/Web/accessibility verification,
|
||||
`0.10.0` public-readiness minor, then `0.10.1`–`0.10.3` fixes, `0.11.0` replay + fourth venue +
|
||||
localization foundation, `0.12.0` signed/storefront builds + save v2, `0.13.x` content complete
|
||||
and release candidates, `1.0.0` stable. The full gap analysis and roadmap live in `PLAN.md`; the
|
||||
character track is versioned now, so a blocked rig cannot block the release. Planning only —
|
||||
none of these versions is built yet.
|
||||
- **Quaternius face/readability gate (2026-11-05):** the base is 6.2k tris total and replaces a
|
||||
library with a sculpted realistic face, so the 0.9.9 gate is a product decision, not just an
|
||||
art check. Owner picks full swap / crowd-only / extra head detail. Do not lower character
|
||||
fidelity silently.
|
||||
- **Pose composition is rest-relative (resolved 2026-11-05):** Godot's `set_bone_pose_rotation`
|
||||
offsets from the bone rest, so the correct swing around a world axis is
|
||||
`rest * Quaternion(rest.inverse() * axis, angle)`. For the old 180° rest this reduces to the
|
||||
legacy `rest * Quaternion(axis, angle)`, so the 13-core fallback is unchanged (commit `8b1bf73`).
|
||||
- **One character source, one fallback:** no new NPC source without an ADR. MPFB body work stays
|
||||
closed, the Stage-1 proportion warp is not reopened, and the procedural `npc_base` + MakeHuman
|
||||
head stay tested until the Quaternius base passes its gate.
|
||||
|
||||
- Engine: Godot 4.3 stable (official editor build, `~/.local/bin/godot`). The system's preinstalled `godot-runner` (4.7.2, Fedora) cannot load projects from arbitrary paths because it was compiled without path overrides; use the official editor binary instead.
|
||||
- Language: GDScript. All cross-file references use explicit `preload()` constants (not `class_name` globals) so scripts compile in headless `--script` runs without the editor's class cache.
|
||||
- Test framework deviation: the project uses a custom minimal headless test runner (`src/test/test_runner.gd`) instead of GUT, because GUT needs the editor plugin. CI runs it with `--headless`.
|
||||
- Eventing: no autoload signal bus. `ShiftCoordinator` owns an `EventBus` instance (`src/core/event_bus.gd`) passed to subsystems; subsystems emit only when a bus is assigned. Autoloads at parse time are unreliable under `--script`.
|
||||
- Static signals are not available in Godot 4.3 GDScript ("Expected func or var after static") — do not use them.
|
||||
- GDScript lambdas capture local variables **by value**; mutating a captured local inside a lambda does not affect the outer variable. Use member functions for accumulators in tests.
|
||||
- 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.
|
||||
## Gotchas / Known Issues
|
||||
|
||||
- **Desktop HQ preset (v0.7.1):** `packaging/desktop_hq/override.cfg` switches a
|
||||
copied executable to Forward+; `Materials.forward_plus_active()` gates
|
||||
volumetric fog/SDFGI and `light_volumetric_fog_energy`, and
|
||||
`Materials.modern_light_falloff()` compensates for Godot 4.7's much harsher
|
||||
local-light falloff. LATE volumetric density is 0.020 with flashlight
|
||||
volumetric energy 3.0. `tools/build_releases.sh` packages the Linux/Windows
|
||||
binaries + override into `gig-line-desktop-hq-<version>.zip`;
|
||||
`publish_release.sh` uploads it when present. A/B report:
|
||||
`docs/screens/realism/2026-11-01-forward-plus/` (entrance gap 1.568→1.187).
|
||||
- **Local Godot toolchain (2026-11-01):** `~/.local/bin/godot` is 4.7.2 stable;
|
||||
4.3 stable is kept as `~/.local/bin/godot-4.3.stable`. Export templates for
|
||||
4.7.2 are installed under `~/.local/share/godot/export_templates/4.7.2.stable`
|
||||
(4.3 remains). The project and CI still pin 4.3 in `project.godot`/the Gitea
|
||||
workflow; 4.7.2 imports, runs the 231-test suite, session-shell smoke and a
|
||||
Linux export successfully. Godot 4.7 writes `*.gd.uid` files; they are kept
|
||||
out of this clone's status via `.git/info/exclude` until the project upgrades.
|
||||
- **Blender invocation (v0.6.0):** `flatpak run org.blender.Blender --background --python
|
||||
/run/media/system/DATA/Documents/06-games/security-game/tools/npc_pipeline.py -- build`.
|
||||
The flatpak cannot read `/tmp`; use absolute repo paths. `assets/models/npc/.gdignore` is
|
||||
required, otherwise Godot's editor tries to import the committed `.blend` and hangs during
|
||||
`--import` (also add `.gdignore` to local build/squashfs folders when scanning is slow).
|
||||
- **Skinned NPC integration (v0.6.0):** `NpcAvatar._build_skinned_body()` loads
|
||||
`npc_base.glb` and keeps `_build_body()` as fallback (`use_skinned_model=false` in tests).
|
||||
Bone pose rotation is the **absolute local orientation**, not an offset from rest: compose with
|
||||
`get_bone_rest(i).basis` or the whole body snaps to a broken pose. Proxies named Hip/Knee/
|
||||
Shoulder/Elbow/Hand + Head marker preserve the old animation math; `Rig/Head` is the marker.
|
||||
Five material slots are tinted via `SurfaceMaterials.npc_slot()`; keep `_rig.scale` uniform on
|
||||
the skinned path. Budget: 16 avatars = **144 meshes**, ~6.9 ms headless.
|
||||
- **NPC facing (fixed after v0.5.0):** the avatar's visual front is **+Z** (procedural eyes
|
||||
and the Blender model's face). Godot `look_at()` aims **-Z**, which made every NPC walk
|
||||
backwards. Use `NpcAvatar.yaw_towards(dir)` and set `rotation.y`; do not call `look_at()` on
|
||||
avatars. Head-turn offsets use `+basis.z` for the same reason. Covered by
|
||||
`test_yaw_faces_the_movement_direction`.
|
||||
|
||||
- **Image root (ADR-0014, 2026-11-01):** `assets/images/` is the runtime root for composed
|
||||
interface/scene art (`ui/`, `scenes/`); tiled PBR textures stay under `assets/textures/`;
|
||||
`docs/screens/designs/` and `docs/screens/concepts/` stay reference-only; QA captures stay in
|
||||
`docs/screenshots/`. When a mockup becomes runtime art, copy/process it into `assets/images/`
|
||||
with a new name (no overwrites) and keep a fallback in code.
|
||||
**Mockup copies:** 21 design references live in `assets/images/ui/`; `docs/screens/designs/`
|
||||
remains the reference-only copy. `tools/screen_designs.py` reads/writes the runtime root.
|
||||
|
||||
## Handoff (read this first)
|
||||
|
||||
- **Status (2026-11-01, after the venue-geometry pass):** v0.7.0 is released. Post-v0.6.0 the
|
||||
realism pass, the visual-alignment pass and the NPC face/hand pass all landed. Headless
|
||||
**229/229**, Python tooling **87/87**, `tools/check_screenshots.sh` **11 good / 0 bad**,
|
||||
`screen_designs.py compare` 11 good / 0 bad, `scene_concepts.py audit` all GOOD,
|
||||
`blender_assets.py audit` exit 0, `smoke_npc_budget` OK (16 avatars, 6.94 ms of 50 ms).
|
||||
`PLAN.md` is compact; README/architecture track the current controls, asset pipeline, CI and
|
||||
release artifacts. The historical notes below remain useful for implementation gotchas; for
|
||||
current tasks use `TODO.md`.
|
||||
|
||||
- **Phase 2 — AI texturing (this agent, DONE 2026-11-01).** Phase 1 assets were
|
||||
already in the tree (untracked) and are preserved. Generated six seamless AI
|
||||
clothing albedos into `assets/textures/npc/ai/{shirt,trousers,jacket,vest,
|
||||
coat,dress}.png` (512×512, `--role tile`, gpt-5-image-mini; audit 6 good).
|
||||
**Gotcha:** `asset_pipeline.py generate` defaults to `GEN_SIZE=2048`, which
|
||||
gpt-5-image-mini rejects (HTTP 400); pass `--request-size 1024` for 1:1 roles.
|
||||
`generate --role tile` already applies `make_seamless`, so a separate
|
||||
`seamless` pass is redundant. Added to `tools/npc_pipeline.py`: `prepare`
|
||||
(layers AI shirt→cloth, trousers→pants albedos at 128px) and `palette`
|
||||
(writes `assets/textures/npc/palette.json` via `model_pipeline.dominant_colors`).
|
||||
`write_slot_textures()` now skips the albedo for slots with an AI override
|
||||
(normal/ORM stay procedural). Blender `build` re-run: GLB audit ok, 1204 tris /
|
||||
2488 verts, AI albedo embedded for cloth+pants. Phase 2 completed and shipped with
|
||||
the v0.6.0 asset library.
|
||||
**Import concurrency:** a parallel agent is running
|
||||
`timeout 300 godot --headless --editor --quit --path .` and then checks
|
||||
`assets/models/generated/npc_base.glb.import` — treat that as the canonical
|
||||
import. My own `godot --headless --path . --import` hung with no output past
|
||||
the engine banner (two Godot instances contended on `.godot`); I killed the
|
||||
orphan (PID 2482934). Do not run a second import while that `--editor --quit`
|
||||
is active.
|
||||
Next: Phase 3 (`NpcAvatar._load_model` + slot tinting) consumes
|
||||
`palette.json` and the GLB; Phase 2's capture/judge item is blocked on that.
|
||||
|
||||
- **Git hygiene + tooling (2026-11-01):** dev asset folders are now gitignored
|
||||
and untracked: `assets/models/concepts/`, `assets/models/npc/` (Blender
|
||||
sources), `assets/textures/npc/ai/` (raw AI fabrics), and the raw
|
||||
`assets/textures/ai/*` outputs (only `assets/textures/ai/game_ready/` stays).
|
||||
Kept in the repo: `assets/textures/npc/*_{albedo,normal,orm}.png` +
|
||||
`palette.json`, `assets/models/generated/*` (props + npc_base.glb), and the
|
||||
authored `assets/textures/*` set. `tools/asset_pipeline.py generate` gained
|
||||
`--variants N` — generates N alternatives saved as `<stem>-1..N.png` (N=1
|
||||
keeps the unsuffixed path). Python tooling tests still pass (exit 0).
|
||||
|
||||
- **Screenshots + docs (2026-11-01):** regenerated the eleven README captures
|
||||
(`capture_screenshots.gd` + `capture_prop_gallery.gd`) — ten changed, the
|
||||
prop gallery is byte-identical (props unchanged). README gained an NPC
|
||||
character-model subsection, a "Confirmed game assets" section (linked
|
||||
`game_ready` textures, generated models, NPC maps), and refreshed
|
||||
dev-status / not-yet lines; `docs/architecture.md` Level 2 now lists
|
||||
`npc_pipeline.py` + `asset_pipeline.py` and the confirmed-asset locations.
|
||||
|
||||
- Repo: `/run/media/system/DATA/Documents/06-games/security-game` (also reachable at
|
||||
`/var/home/zwitschi/Documents/06-games/security-game`). Godot: `~/.local/bin/godot` (4.3.stable).
|
||||
Tests: `godot --headless --path . --script res://src/test/test_runner.gd` → **177/177**.
|
||||
Python tooling tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs test_model_pipeline` → **53**.
|
||||
Session shell smoke: `godot --headless --path . --script res://src/test/smoke_session_shell.gd` → exit 0.
|
||||
Windowed control smoke: `godot --path . --script res://src/test/smoke_focus_radial.gd` → exit 0
|
||||
(now also covers wheel cycling and right-click clearing).
|
||||
- `main` is in sync with origin. **v0.2.2** tags the session-shell + world-follow-up commit;
|
||||
CI builds the portable binaries, NSIS installer, and AppImage on the tag push.
|
||||
- **v0.3.0 (2026-10-31, working tree):** rendering pass (`src/game/materials.gd`, phase-driven
|
||||
environment), generated `.glb` props (`tools/model_pipeline.py`, `assets/models/generated/`),
|
||||
aim-free selection with wheel cycling and Shift precision walk (ADR-0011/0012), and a **Web**
|
||||
export target. `export_presets.cfg` gains a `Web` preset with `variant/thread_support=false`
|
||||
(static hosting, no COOP/COEP); `tools/build_releases.sh` exports `build/web/` and zips it to
|
||||
`build/gig-line-web-<ver>.zip`, and `publish_release.sh` uploads it. Covered by
|
||||
`src/test/tests/test_web_export.gd`.
|
||||
- **Prop atlas (v0.3.1):** `model_pipeline.atlas_for()` builds a per-prop power-of-two PNG with
|
||||
one 8×8 cell per palette role, plus a metallic/roughness atlas (G=roughness, B=metallic). The
|
||||
GLB merges all parts into one primitive with constant per-vertex UVs (cell centre) and a single
|
||||
material (`baseColorTexture` + `metallicRoughnessTexture`, NEAREST sampling). Colours are sRGB in
|
||||
the texture, so the old `srgb_to_linear`/`_linear_rgba` helpers were removed. `audit_glb` fails
|
||||
if a prop is not exactly one material or misses TEXCOORD_0/images.
|
||||
- **Mockups complete (v0.3.2):** `docs/screens/designs/` holds AI references for all 19
|
||||
catalogue screens. Only the six staged captures (01–06) are compared/judged; the other 13
|
||||
have no `capture`, so adding staged captures for the wand panel, timeline and escalation HUD
|
||||
would extend automated visual checks. `generate` now calls `_load_env_file()`.
|
||||
- **Staged captures (v0.3.3):** `capture_screenshots.gd` now emits nine shots. States 7–10 switch
|
||||
to The Joke Shop at elapsed 620 s (EVENING has routine spawns; LATE has none), retry the wand
|
||||
until it alerts, then jump to 760 s after staging for night lighting. The timeline needs
|
||||
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents. The escalation
|
||||
HUD mockup is third-person now; `judge` still classes it `review` (no diff).
|
||||
- **Prop collision (v0.4.0):** `_add_prop_model()` fits a `StaticBody3D` from the model bounds
|
||||
(cylinder for bollard/bin/hydrant, box otherwise) and adds a `NavigationObstacle3D`; NPC agents
|
||||
enable avoidance and move with the safe velocity from `velocity_computed`. The navmesh stays a
|
||||
flat plane, so avoidance (not a baked mesh) routes guests around props. Placements live in
|
||||
`PROP_PLACEMENTS` with `placement_problems()` validation; variants come from `variant_for(index)`.
|
||||
`smoke_props.gd` must be windowed because the guard reads captured-mouse input.
|
||||
- **Realism pass (2026-11-01, completed + adopted):** `tools/scene_concepts.py`
|
||||
(`list`/`generate`/`process`/`promote`/`audit`/`realism`) never overwrites an image
|
||||
(`next_free_path` adds `-vN`; `process` skips existing unless `--new`; `promote` keeps an
|
||||
existing pick). Raw AI output: `assets/textures/ai/v2/raw/` (gitignored). Candidate pool:
|
||||
`assets/textures/ai/game_ready/v2/` (gitignored) and the **tracked runtime assets** are
|
||||
`.../v2/picks/{skyline_day,skyline_dusk,skyline_night,brick_facade,sidewalk_pavers,asphalt,
|
||||
asphalt_wet,awning_canvas}.png` (+ `_normal`). Concepts `docs/screens/concepts/` (committed,
|
||||
`.gdignore`, targets in its README). Runs `docs/screens/realism/<run>/` (PNG captures
|
||||
gitignored; `report.{json,md}` + `sheet.jpg` committed).
|
||||
`src/test/capture_realism.gd` runs five stages (day 0 s / evening 620 s / late 760 s /
|
||||
joke_shop 700 s / studio 700 s; `--stages=` subset) and swaps textures by the material's
|
||||
*original* albedo path. **Gotcha:** in the SceneTree script, do not overwrite `_phase` after
|
||||
`_begin_stage()` sets `done`, or the run loops forever between stage/shoot (`_queue` empty).
|
||||
**Gotcha:** `elapsed_s = 620` is *Evening* (dusk), 700+ is Late. Godot 4.3
|
||||
`StandardMaterial3D` has no `fog_enabled`; the skyline is a `ShaderMaterial` with
|
||||
`render_mode unshaded, cull_disabled, fog_disabled` + `ALPHA_SCISSOR_THRESHOLD` so the
|
||||
panorama's own haze shows. Backdrop plane is 90 m tall with 2 UV repeats (~200×90 m per
|
||||
repeat, near the 2:1 panorama aspect). Legacy `assets/textures/skyline.png` is no longer
|
||||
loaded but stays on disk for the `test_world` asset check.
|
||||
**Phase look:** day sun 0.62 / exposure 0.6 / ambient 0.22; evening sun 0.12 / exposure
|
||||
0.72 / dark violet sky; late exposure 0.95 / ambient 0.10 with the wet asphalt street and a
|
||||
bright skyline tint (1.5) so the night windows read. Stripes: `stripe_period()` (detrended
|
||||
autocorrelation) + `align_stripes()` (resample to whole periods) replace the edge crossfade
|
||||
for `awning_canvas`; `audit_striped_tile()` compares the wrap step with the harshest interior
|
||||
step.
|
||||
**Judge:** the default `google/gemini-2.5-flash-lite` mostly works but intermittently answers
|
||||
HTTP 400 "API key not valid" (one shot of ten on 2026-11-01); re-run or pass
|
||||
`--model google/gemini-2.5-flash` when it does. Scores are noisy (±1–2), so
|
||||
`realism --repeats 3` averages and `summarize()` reports a like-for-like mean over the shots
|
||||
every set captured. Final: overall 0.8→1.0, backdrop 0.6→1.6, metric gap 0.444→0.348.
|
||||
`screen_designs.py judge` gained one retry for the same transient 400.
|
||||
**Character portrait capture (2026-11-01):** `capture_realism.gd` has a `portrait` stage
|
||||
(`npc_portrait` -> concept `npc_portrait_day`) because crowd shots cannot judge avatars. It
|
||||
freezes one NPC, parks the rest of the crowd 140 m away (a passer-by otherwise blocks the
|
||||
frame), moves the subject to an open sidewalk patch, turns it into the camera and stands the
|
||||
guard on the sunlit side. Needed because the NPC FSM re-writes `rotation.y` every physics tick
|
||||
(so the yaw must be set after `set_physics_process(false)`), the venue's morning sun leaves a
|
||||
face in shadow from most angles, and `visible = false` does not keep the crowd out of frame.
|
||||
`--portrait-type=N` forces a role; hooded roles (trespasser/violent/crisis) are skipped.
|
||||
**Flashlight (2026-11-01, final):** a *tight* `SpotLight3D` on `Head`: `spot_range` 20,
|
||||
`spot_angle` 14, `light_energy` 4.2, `spot_attenuation` 0.9, forward `rotation_degrees = 0`,
|
||||
starts invisible. A fake additive light shaft was tried twice (cylinder with a UV ramp, then a
|
||||
vertical-fade `ShaderMaterial`) and **rejected both times**: on GL Compatibility a first-person
|
||||
camera inside the cone renders it as a filled disc and the far ring shows, so
|
||||
`Materials.light_beam()` and the beam mesh are gone. Regression guard:
|
||||
`test_flashlight_aims_a_tight_forward_spot` in `src/test/tests/test_door_and_flashlight.gd`
|
||||
(lamp on the head, aims straight, cone ≤ 20°, starts off). Night captures are staged in LATE
|
||||
(`elapsed_s` 760), not dusk.
|
||||
**NPC detail pass (2026-11-01):** `_face()` now builds sclera + iris + upper lid per eye, brow
|
||||
ridge, cheekbones, nose bridge/tip, ears with lobes and split upper/lower lips; the new
|
||||
`eye_white` material is a *fixed-colour* slot, so `_apply_outfit_materials()` leaves it white.
|
||||
`_hands_and_shoes()` has four fingers plus a thumb and `_clothing()` adds `Cuff` tori.
|
||||
Budget work: `CHAR_MAX_VERTS` 5000 (see the library note), body decimation 0.26, upper lids are
|
||||
boxes rather than spheres, and knuckle spheres/nostrils were cut to stay inside it.
|
||||
**Orientation debugging:** to check a GLB's front axis, compare the mean Z of the `eye_white`
|
||||
surface (face) and of the `rubber` sole verts (toes) — all 8 avatars are +Z, so any
|
||||
"they walk backwards" symptom is a yaw bug, not an export flip.
|
||||
- **Blender asset library (v0.6.0, integrated 2026-11-01):**
|
||||
`tools/blender_assets.py` (`build`/`render` in Blender; `audit`/`sheet`/`list` stdlib) writes
|
||||
`assets/models/blender/{npc,npc_gear,props}/*.glb`, `manifest.json` (integration source of
|
||||
truth), sources in `assets/models/blender_src/` (`.gdignore`d), and review sheets
|
||||
`docs/blender_previews/sheet_*.jpg` (per-asset PNG renders are gitignored). 8 characters, 18
|
||||
gear, 20 props; notes in `docs/blender-assets.md`. Characters are drop-in for `npc_base`
|
||||
(same mesh node name, bones at the same rest positions, five tint slots) and stay under the
|
||||
6000/4000 budget only because of body decimation (0.26) + `smart_project` at 80°: glTF vertex
|
||||
count includes UV-seam splits, so extra beveled detail quickly breaks the limit. The 2026-11-01
|
||||
face/hand pass raised `CHAR_MAX_VERTS` to 5000 (tris 3,974–5,026) after measuring 16 avatars at
|
||||
~6.9 ms of a 50 ms frame; `npc_pipeline.MAX_VERTICES`/`MAX_TRIANGLES` (base mesh) are unchanged.
|
||||
**Gotchas:** `bisect_plane` and thin bevels leave zero-area triangles (Godot warns "non-finite
|
||||
normal in LOD generation"); `_clean()` + a weld after bisect fix it, and the audit rejects them.
|
||||
Fixed-colour detail materials carry a 4×4 texture because `_apply_outfit_materials()` sets
|
||||
`albedo_color = WHITE` on unknown slots. Cycles CPU render ≈ 3 s/asset; Godot import of the
|
||||
library is clean.
|
||||
**Integration:** `NpcAvatar.character_model_for()` selects a variant by role/seed; only the
|
||||
five contract slots are tinted (detail materials untouched; `npc_vest` keeps its fixed hivis
|
||||
vest, firefighter/guard get dark uniform tints). Gear is attached at head/back/chest/hand and
|
||||
falls back to primitives; `npc_hoodie` skips the extra hood gear. `WorldBuilder` prefers
|
||||
`assets/models/blender/props/` for the six replacements and the new street dressing, fits the
|
||||
same colliders/obstacles, and collects library emissives for `set_night_mode()`.
|
||||
`LIBRARY_TRIANGLE_BUDGET` = 60k (current ~27,986). Library props are excluded from
|
||||
`PROP_PLACEMENTS` only when they need dedicated slots (door/marquee/sedan/handrail/lantern).
|
||||
**Gear fit (v0.6.1):** `fit` measures head width (0.315 m), skirt/hip radius and gear bounds;
|
||||
sunglasses are 0.202 m, the bag's body inner face is 0.262 m (coat 0.225, dress 0.260), and the
|
||||
helmet is lifted 2 cm above the brow. `test_npc_character` guards head/back gear in Godot.
|
||||
Blender's GLB export is byte-nondeterministic for unchanged assets (padding/index order), so do
|
||||
not commit a full no-op rebuild; rebuild only the assets whose geometry changed.
|
||||
- **Venue geometry (v0.6.1+):** `WorldBuilder.VENUE_SPECS` is the per-venue table; `WorldBuilder.new(root, id)` / `set_venue()` select it, unknown ids fall back to `hollywood_building`. Dimensions, window/pilaster rows, stairs, street, nav bounds, guard spawn, feature flags and prop placements are read from the spec. `GameManager.world_root` holds only the world; `_rebuild_world_for_venue()` frees/recreates it on venue selection/cycling so guard/UI survive. Joke Shop uses procedural alley walls/shutters/dumpster/AC; TV Studio uses a stone facade, bulb canopy, car paint and barrier placements. Gallery shots 12/13 use the existing `joke_shop_queue`/`studio_arrival` references.
|
||||
- **Screen-design judge (2026-11-01):** `screen_designs.py judge` could not run because `OPENROUTER_API_KEY` returned HTTP 400 "API key not valid". `check_screenshots.sh` still passed 13/13; the judge retry is a TODO follow-up.
|
||||
- **Portrait capture:** `capture_realism.gd` faces the **Rig** toward the camera (using the
|
||||
inverse look direction) and hides the blue `FocusCircle`; the old code used the wrong yaw and
|
||||
left the focus disc over the face. An explicit `--portrait-type=N` now overrides the hooded
|
||||
skip, but the day roster spawns none of types 5-7, so a hooded close-up is deferred.
|
||||
- **v0.6.0 environment pass:** `tools/bake_ao.py` bakes 256² AO maps per library asset
|
||||
(`<name>_ao.png`, red channel) and `Materials.apply_ao()`/`SurfaceMaterials` wire them in;
|
||||
`tools/make_normal_maps.py` derives tangent normals for the 1024² brick/concrete/asphalt
|
||||
textures. Forward+ boots on desktop but is rejected for v0.6.0 (project-wide renderer, Web
|
||||
must stay GL Compatibility). Headless 222/222, Python 69/69.
|
||||
- **Prop pipeline (done):** audit enforces 400 tris / 600 verts, ground contact, UV range, no
|
||||
degenerate tris, one material. `capture_prop_gallery.gd` writes `10-prop-gallery.png` (3x2 studio
|
||||
line-up, back row on a platform) and the `prop_gallery` catalogue entry compares it.
|
||||
- **NPC character pass (v0.5.0):** Rig is articulated (`Rig/Hip* -> Knee`, `Shoulder -> Elbow ->
|
||||
Hand`); `outfit_for` adds layer/hair_style/hand_item; `_idle_layer` drives phone/clipboard/crossed
|
||||
and escalation gestures; `_reaction_tag` shows `!!`, `?!`, `!`, `?`; `apply_lod` swaps to a
|
||||
billboard impostor at 26 m (hysteresis 1.5 m); GameManager `_refresh_queue` assigns queue offsets.
|
||||
Perf: 16 avatars = 534 meshes, ~6.9 ms headless.
|
||||
- **Player movement (v0.5.0):** **W was inverted** (`get_axis(forward, back)` is negative for W and
|
||||
the code multiplied it directly); now `forward * -input_dir.y`. Step-up uses test_move + a raised
|
||||
copy (0.32 m). Guard emits footstep/landed/precision_changed; GameManager forwards them to
|
||||
AudioFeedback and the HUD. Gamepad actions are added at runtime by
|
||||
`GuardController._ensure_gamepad_bindings`; remaps persist via `Save` `input_bindings` and are
|
||||
applied by `InputBindings.apply` (key/button events only). Windowed `smoke_movement.gd` guards it.
|
||||
- **Scheduled TODO pass complete:** only the optional Future ideas remain (live replay, extra
|
||||
venues, voice audio, image-to-mesh). Grounding facts: the
|
||||
NPC rig is rigid capsules with a whole-limb swing, and `guard_controller.gd` uses one `accel`
|
||||
value. Each task should get its own windowed smoke or capture where it changes feel/looks.
|
||||
- **Backlog complete:** every planned TODO section is done and moved to `DONE.md`; `TODO.md`
|
||||
now only lists optional future ideas (live 3D replay, NPC LOD, more venues, real audio, prop
|
||||
atlas, AI mockups for the new catalogue entries, image-to-mesh props). GUT was consciously not
|
||||
adopted (custom runner covers it without an editor plugin).
|
||||
- Do not re-open `override_access`. Do not bind wand to `W`. Do not remove the radio easter egg.
|
||||
|
||||
## UI / Presentation Decisions
|
||||
|
||||
- Runtime UI/scene art is loaded through `src/game/ui/art.gd` (`assets/images/ui/` then `scenes/`); missing files return null so panels keep procedural fallbacks.
|
||||
- Screen mockups with baked-in controls are used as backdrops plus an opaque `ArtPanel` cover, not as clickable full-screen skins.
|
||||
- Radio, wand, inspection, and body-camera timeline panels use their reference art as backdrops; forms sit on transparent, padded panels over the covered mockup controls.
|
||||
- HUD target names use a boxed chip under the crosshair; escalation has a five-segment bar below the text label. The body-camera REC/clock block starts at y=92 on the right so it clears the shift/rep/crowd text.
|
||||
- Staged captures in `docs/screenshots/` are committed and were last regenerated after the realism and NPC passes; re-run `tools/check_screenshots.sh` (not a partial capture) before replacing them.
|
||||
- `tools/check_screenshots.sh` is the repeatable capture+compare smoke; both capture scripts accept `--out=res://docs/screenshots`. Judge verdicts on the staged set were 7 match / 4 review / 0 diff.
|
||||
- Venue selection used to leave `guard.input_locked` true and the mouse visible; `_start_shift()` must call `_refresh_freeze()` after starting the clock.
|
||||
|
||||
## Current State
|
||||
|
||||
- **v0.3.0 pass (2026-10-31, historical; shipped):** rendering, generated prop models, screen
|
||||
catalogue, aim-free selection. See ADR-0011 (selection) and ADR-0012 (materials/props).
|
||||
- `src/game/materials.gd` (`SurfaceMaterials`) is now the single source of surface
|
||||
recipes + `configure_environment()`. `GameManager.PHASE_LIGHTING` gained
|
||||
`exposure/glow/fog_color/fog_density/fog_energy/contrast/saturation/ambient_energy`.
|
||||
Renderer is **GL Compatibility**, so SSAO/SSIL are unavailable; glow+fog+ACES are used
|
||||
instead and unsupported settings are ignored rather than gated in code.
|
||||
- World: scaled brick (uv 22,10), framed windows with per-window night emission
|
||||
(`i % 3 != 2` lit), stone base course, door trim/handles, curb, lane dashes (the old
|
||||
loop had `Vector3(i * 0.0, …)` and stacked every dash at x=0), street lamps use the
|
||||
metal profile. The two flat planters that clipped the steps were removed.
|
||||
- `tools/model_pipeline.py` builds `assets/models/generated/*.glb` (bollard, bench,
|
||||
trash_bin, planter_box, newspaper_box, fire_hydrant) from box/cylinder specs;
|
||||
`WorldBuilder._add_prop_model()` loads them with a procedural fallback and records
|
||||
`loaded_models` / `fallback_props`. **Gotcha:** glTF `baseColorFactor` is LINEAR —
|
||||
writing sRGB made every prop pastel; `srgb_to_linear()` is applied in `build_glb`.
|
||||
**Gotcha:** a new/changed `.glb` needs `godot --headless --path . --import` before
|
||||
`ResourceLoader.exists()` / `load()` sees it (test_generated_props fails otherwise).
|
||||
- `model_pipeline.py concept` (OpenRouter, reads repo `.env`) writes per-prop concept
|
||||
sheets to `assets/models/concepts/`; `dominant_colors()` centre-crops and weights by
|
||||
saturation (a grey studio backdrop otherwise wins), `assign_roles()` maps colours by
|
||||
hue/value. Delete a concept file to fall back to the catalogue palette.
|
||||
- NPC avatars: `outfit_for(type, seed)` (pure) gives shirt/trousers/hair/skin +
|
||||
accessory (cap, hood, backpack, sunglasses, lanyard) and height scale; `_arms` and
|
||||
`_legs` now swing (`limb_swing`) while walking. Keep `Rig/Head` named for focus.
|
||||
- Selection: `NpcFocus.rank_indices` (angle then distance) — `pick_index` = first.
|
||||
`GuardController.cycle_target(direction)` walks nearest-first `nearby_candidates()` and
|
||||
sets `target_pinned`, which makes `_update_target()` skip the cone. `clear_target()`
|
||||
sets `focus_suppress_s = 0.75` so the cone does not instantly re-acquire the person.
|
||||
New input actions `walk_slow` (Shift), `cycle_target_next` / `cycle_target_prev`
|
||||
(wheel down / up). Right click with nothing open clears focus (Esc still pauses).
|
||||
- HUD: `nearby_label` + `set_nearby_hint(count)` show `hud.nearby`; `hud.controls` and
|
||||
`hud.targeting` mention the wheel and Shift.
|
||||
- `tools/screen_designs.py`: catalogue now 19 screens (added wand_screening,
|
||||
joke_shop_queue, studio_arrival, crowd_panic, responder_arrival,
|
||||
body_camera_timeline, escalation_hud), new `list` command, `generate --only/--force`
|
||||
(existing designs are kept by default so a draft run cannot clobber AI mockups),
|
||||
and `judge --dry-run` no longer raises `KeyError: 'class'`.
|
||||
- Screenshot tool leak: `capture_screenshots.gd` intermittently prints
|
||||
`ObjectDB instances leaked at exit`. Verified against a `git worktree` of HEAD~ — the
|
||||
baseline leaks more often (3/3 vs 1/3), so it is a pre-existing quit-timing artifact,
|
||||
not a regression. Normal boots are clean.
|
||||
|
||||
- Access artifacts: `src/core/access_loadout.gd` builds from event `override_access` + venue policy.
|
||||
Spawn signal is now `(profile, intent, npc_id, event)`. Recognition is not auto-attached to every
|
||||
guest. `AccessVerifier.verify(..., already_screened=false)` so Joke Shop scoring uses
|
||||
`controller.screened`. Joke Shop/Studio gained `sidewalk`/`steps_end` waypoints. Wand venues roll
|
||||
`carried_items` per spawn (seeded). Tests in `src/test/tests/test_access_loadout.gd`.
|
||||
- Wand shortcut is **`C`**, not `W` (`W` is walk). `radio_emergency` input map no longer includes W.
|
||||
`hud.controls` / `hud.screen_first` updated.
|
||||
- Control pass (historical; smoke-tested): cone focus via `src/game/npc_focus.gd`,
|
||||
accel/decel, gravity only airborne, `input_locked` freeze, Esc no longer on the guard.
|
||||
`NpcAvatar` in group `npc`; neck/hair/eyes/shoes; blue focus circle + `Label3D` name; `set_focus_visual`.
|
||||
`src/game/ui/action_radial.gd` doughnut (LMB/Space open, A/D/arrows, Space/click confirm, Esc/RMB
|
||||
cancel). Crosshair CanvasLayer. Emergency still `R` only. Number keys 1–6 when the wheel is closed.
|
||||
- Named input actions now exist and are used: `action_menu`, `confirm`, `cancel` (Esc + RMB),
|
||||
`inspect` (E), `wand` (C). The old unused `interact` action was removed.
|
||||
- Runtime fixes from the smoke: `Head` is +1.6 above the guard root (root sits on the floor); guard
|
||||
spawns in front of the door facing the street; building volume moved to z ∈ [-8,-4] so the door,
|
||||
awning, and marquee are visible; step treads rise toward the door; `ShiftCoordinator` pulls the
|
||||
event scheduler once per simulated second (was every physics frame → >1200 NPCs in seconds);
|
||||
`GameManager.MAX_LIVE_NPCS = 16` caps live avatars.
|
||||
- **Session shell (2026-10-31, historical):** the game boots into `StartMenu`
|
||||
(`src/game/ui/start_menu.gd`, layer 20) instead of a live shift. `GameManager._on_venue_selected()`
|
||||
builds the scenario and calls `_start_shift()`; `_shift_running` gates input and `_refresh_freeze()`.
|
||||
Esc with nothing open now pauses (`PauseOverlay`, layer 18) → resume / restart / settings /
|
||||
quit-to-menu. `SettingsPanel` (layer 22) writes `look_sensitivity` + `hud_font_scale` through
|
||||
`Save.save_settings()` → `user://settings.json`; `Save.load_settings()` merges over
|
||||
`Save.DEFAULT_SETTINGS` on boot and `GameManager._apply_settings()` pushes them into the guard/HUD.
|
||||
Shift review "Run another shift" now calls `_show_start_menu()` (not a silent restart). `V` stays a
|
||||
debug venue bind. Smoke: `src/test/smoke_session_shell.gd` (menu → shift → pause → settings → menu).
|
||||
- **World follow-ups closed (2026-10-31):** skyline backdrop restored with `T_SKYLINE`
|
||||
(`assets/textures/skyline.png`, regenerated RGBA with a transparent sky) and an
|
||||
`TRANSPARENCY_ALPHA_SCISSOR` unshaded material (`_skyline_mat`); steps + landing are a
|
||||
`StaticBody3D` with per-tread `BoxShape3D` colliders; marquee `font_size` 160 / outline 24; focus
|
||||
circle (blue disc in front of the face) + name tag 48px; `NpcAvatar.ephemeral`/`lifetime_s` despawns routine traffic
|
||||
(authored events stay). Guard spawn height verified on the landing (guard_y=0.84).
|
||||
- **PLAN verbs (2026-10-31, historical):** the radial has 12 slices
|
||||
(`Stop, Look, Point, No, Ask, Inspect, Wand, Warn, Block, Reason, Recognize, Radio`).
|
||||
`eye_contact` reuses the Level 1 reaction table via `_apply_interaction`. Recognition is a player
|
||||
decision: `NPCController.player_recognized` + `IncidentRecorder.add_access_evidence`, and
|
||||
`GameManager._artifacts_for()` prepends a `RECOGNITION` artifact only after the player declares
|
||||
it. The body camera is a real flag (`GameManager.body_camera_on`, `Hud.set_body_camera`), off by
|
||||
default, `B` toggles it, and a formal warning arms it and writes a `body_camera` incident event.
|
||||
Blocking also calls `_station_between()` to physically place the guard 0.9 m from the NPC toward
|
||||
the entrance. `_request_reason()` logs the NPC's seeded `dialogue_lines` pick.
|
||||
- **NPC navigation was dead (fixed 2026-10-31):** avatars never moved in the live game, for three
|
||||
stacked reasons. (1) `agent.target_position` was re-assigned every physics frame, which
|
||||
invalidates the path so `get_next_path_position()` keeps returning the current point — only set
|
||||
it when the target changes. (2) Nav path points sit on the navmesh plane (y=0) while bodies stand
|
||||
at y≈0.5, so a 3D "are we there" test always looked 0.5 m away and the horizontal direction was
|
||||
zero — compare horizontally. (3) `path[0]` is the old start point, so picking any point "far
|
||||
enough away" can pick the point behind you and oscillate — pick the first point that is
|
||||
**closer to the goal** than the current position. `_nav_try_frames`/`_move_direct` fallback is
|
||||
still there.
|
||||
- **Crowd reactions listen on the EventBus:** `CrowdSystem.apply_event()` emits
|
||||
`event_bus.crowd_changed`; its own `changed` signal is never emitted. `GameManager._connect_crowd()`
|
||||
therefore connects to `coordinator.event_bus.crowd_changed` (reconnect after every
|
||||
`coordinator.setup()`, which recreates both crowd and event bus).
|
||||
- **Crowd presentation:** `NpcAvatar.start_rubberneck(at)` (idle bystanders away from the door stop
|
||||
and face the incident for 12 s) and `start_flee(from)` (panic: walk away at 2.4 m/s, then free
|
||||
themselves). Someone `awaiting_interaction` within 3 m of the door is "engaged" and does not
|
||||
rubberneck. Use `position` (not `global_position`) for that distance check so it also works in
|
||||
headless tests outside the tree.
|
||||
- **Perf:** a busy 16-NPC entrance measured ~1.2 ms/frame (824 fps with vsync off, 800 samples),
|
||||
so LOD/culling is not warranted at `MAX_LIVE_NPCS = 16`. Re-measure before raising the cap.
|
||||
- **Incident recording was dead:** `IncidentRecorder.start_incident()` was never called, so every
|
||||
`add_event`/`add_access_evidence` silently no-opped and the review always showed 0 incidents.
|
||||
`_begin_incident()` now starts one per focused NPC (`has_open_incident()` guard), verbs call
|
||||
`_ensure_incident()`, and `ShiftCoordinator._finish_shift()` closes leftovers as `unresolved`
|
||||
before building the review.
|
||||
- **Editor-ready venues (2026-10-31):** `tools/export_venues.gd` (a SceneTree script) saves the
|
||||
three factory scenarios to `content/venues/<id>.tres` with `ResourceSaver`; the `.tres` files
|
||||
round-trip through `ResourceLoader` (events, profiles, policy, shift phases, door lines,
|
||||
locations). `test_venue_resources.gd` also fails if the exported files are missing or stale
|
||||
relative to the factory, so re-run the export after changing `ScenarioFactory`.
|
||||
- **Vision judge (2026-10-31):** `python3 tools/screen_designs.py judge` sends the capture plus
|
||||
its concept mockup to a vision model (`DEFAULT_JUDGE_MODEL = google/gemini-2.5-flash-lite`,
|
||||
override with `--model`; `openai/gpt-4o-mini` also works). Reads the repo `.env` via
|
||||
`_load_env_file()` (never overrides real env). Verdicts are classed `match` / `review`
|
||||
(mismatch below `--fail-confidence`, default 0.6) / `diff` (confident mismatch → exit 1) /
|
||||
`unknown`. `--dry-run`, `--json`, `--only` supported. The prompt explicitly ignores art style,
|
||||
because the references are stylised mockups; the live run gave 3 match, 3 review, 0 diff.
|
||||
Python tests: `cd tools && python3 -m unittest test_asset_pipeline test_screen_designs`.
|
||||
- **String formatting safety (2026-10-31):** `Strings.placeholder_count()` counts `%` specs
|
||||
(ignoring `%%`), `fmt()` warns and returns the template when the arg count does not match
|
||||
(instead of raising an engine error), and `validate()` lints the table for trailing `%`,
|
||||
unsupported conversions, and mixed positional/sequential placeholders. Tests cover all three
|
||||
plus a formatting pass over every placeholder key with type-correct dummy args.
|
||||
- **Authored scenario content (2026-10-31):** events can now carry `hint` (a string key logged
|
||||
when the avatar spawns), `extra_spawns` (celebrity arrivals pull in tourists/influencers via
|
||||
`_spawn_npc` with `kind: routine`), and `refuses_screening` (copied to
|
||||
`NPCController.refuses_screening`; `_toggle_wand_screening` logs `wand.refused`, records a
|
||||
`wand_refused` event, applies a small prop hit, and does not open the panel). `film_boundary`
|
||||
is a new intent that maps to `steps_end` so the influencer stands past the posted line.
|
||||
- **Accessibility (2026-10-31):** escalation text is `"<name> [###--] L3/5"` via
|
||||
`Hud.escalation_meter(level)` (pure, tested) so the cue survives grayscale. HUD font scale uses
|
||||
`add_theme_font_size_override` on the core labels (`set_font_scale`), **not** `Control.scale`,
|
||||
which pushed the right-anchored clock label off-screen. Settings load before `_build_ui()`, so
|
||||
`_ready()` re-applies player settings after the HUD exists.
|
||||
- **Audio (2026-10-31):** `AudioFeedback.start_crowd_bed()` builds a 4 s looping `AudioStreamWAV`
|
||||
(one-pole-filtered noise + 68/103 Hz drone, seeded) and `set_crowd_level(mood)` maps 0..1 to
|
||||
-34..-12 dB. `play_radio_open/close` push short filtered-noise bursts. All procedural; with a
|
||||
dummy (headless) audio server the calls are no-ops. `_free_player()` returns null when `_ready`
|
||||
has not run (tests), and callers handle null.
|
||||
- **NPC theater (2026-10-31):** `NpcAvatar.theater_pose()` maps `intent` + `arrived` to
|
||||
idle/sit/photo/film/perform. Pure helpers `crouch_for_pose`, `prop_lift_for_pose`,
|
||||
`sway_for_pose` keep the math testable. Sit lowers the rig by 0.34 and swings the legs forward
|
||||
(negative X pitch; positive looked like kneeling). `_photo_prop` is set for influencer/tourist
|
||||
cameras, `_sign_prop` for protester/performer boards; their base Y values are captured at build
|
||||
time so the animation offsets from rest.
|
||||
- **Body-camera timeline (2026-10-31):** `src/game/ui/event_timeline.gd` flattens
|
||||
`review.incidents.incidents[].events` into one time-ordered list (`flatten`), formats each row
|
||||
with `describe`, and steps with Left/Right or the buttons. `ShiftCoordinator.started_at_ms`
|
||||
gives the review an absolute start for mm:ss stamps. `GameManager._open_timeline` hides the
|
||||
review dim (two stacked overlays made the screen nearly black) and `_timeline_from_review`
|
||||
restores it on close. Smoke: `src/test/smoke_timeline.gd`.
|
||||
- **Radio dispatch was dead after the first shift:** `coordinator.setup()` builds a new
|
||||
`RadioSystem`, so the `responder_arrived` connection made in `_ready` was lost on every venue
|
||||
switch/restart (responders never resolved anything). `_connect_radio()` now re-wires it next to
|
||||
`_connect_crowd()` after every setup. Same class of bug as the crowd signal.
|
||||
- **Emergency aftermath:** `ShiftCoordinator.responder_outcomes` / `investigations` feed the review.
|
||||
`evacuation` (emergency call, low danger, no weapon) locks the door, makes every NPC flee, marks
|
||||
them `leave_requested`, and applies a `security_failure` hit. An emergency placed while
|
||||
`current_npc_truth` has no weapon and `danger_level <= 1` appends an investigation note, records
|
||||
`investigation_opened`, and applies `false_report` -6.
|
||||
- **Skyline backdrop (2026-10-31):** `PlaneMesh` defaults to a **horizontal** XZ plane — set
|
||||
`mesh.orientation = PlaneMesh.FACE_Z` or the "backdrop" renders as a band overhead. The skyline
|
||||
is now a ring of four 400x60 planes at 200 m (center y=30, so building bases meet the ground),
|
||||
`TEXTURE_FILTER` default, `TRANSPARENCY_ALPHA_SCISSOR` with threshold **0.5** (0.3 left faint
|
||||
mip dashes across the sky), unshaded with a lifted albedo (`Color(2.6,2.7,3.0)`) so it is not
|
||||
pure black. Ground is 500x500 so no sky gap shows under the buildings. The `skyline.png` import
|
||||
needs `mipmaps/generate=true`.
|
||||
- **Asset gotcha:** `assets/textures/game_ready/` is *not* imported (no `.import`). Use the
|
||||
imported `assets/textures/skyline.png` path. Untracked duplicate folder should not be referenced.
|
||||
- Host sign-in follow-up is **done**: `REQUIRE_SIGNIN_VIEW = false` in `/etc/gitea/app.ini`
|
||||
(backup `app.ini.bak-*` alongside), Gitea restarted; anonymous repo page/API return 200 and
|
||||
release assets download in full. Reverse by setting it back to `true` and restarting.
|
||||
- **Release publishing was broken since v0.2.2 (fixed 2026-10-31):** `publish_release.sh`
|
||||
only built `AUTH` in the `GITEA_TOKEN` branch; the `~/.git-credentials` branch set `CRED`
|
||||
and then used unbound `${AUTH[@]}` under `set -u`, so every API call failed and the tag
|
||||
job ended "could not create or find release". The repo also has **no** `GITEA_TOKEN`
|
||||
secret (`/actions/secrets` is empty) and the runner has no git credentials. Fixes: the
|
||||
script sets `AUTH=(-u "$CRED")`; the workflow falls back to `github.token`
|
||||
(`secrets.GITEA_TOKEN || github.token`) — but `github.token` is **not** provided by this
|
||||
Gitea/act runner, so the env dump showed no token and the v0.2.10 tag build failed again.
|
||||
Final fix: `publish_release.sh` falls back to the **local git extraheader** that
|
||||
`actions/checkout` writes (`git config --local --get-regexp '^http\..*\.extraheader$'`),
|
||||
which carries the runner's basic-auth credentials for the same server. Verified locally by
|
||||
adding that extraheader and publishing v0.2.10 (all five artifacts). **v0.2.9 and v0.2.10
|
||||
are published**; v0.2.4–v0.2.8 tags have no artifacts. Verify the CI path on the next tag.
|
||||
- **CI publishing verified (2026-10-31):** tagging v0.2.12 produced a release with all five
|
||||
artifacts automatically (no repo secret), confirming the checkout-extraheader fallback.
|
||||
- **Host access (2026-10-31):** `root@192.168.88.30` now works from this workstation (key auth),
|
||||
host name `gitea`, Gitea 28.2.0. Maintenance applied: `apt-get clean`, journald capped to
|
||||
`SystemMaxUse=300M` (`/etc/systemd/journald.conf.d/99-size.conf`, journal ~133M), disk at
|
||||
~7.6G free of 16G.
|
||||
- **Host disk resolved (2026-10-31):** the Gitea VM root filesystem was expanded from 7.8G to
|
||||
16G (`/dev/mapper/pve-vm--500--disk--0`, now ~7.8G free). The pruned **v0.2.0** and **v0.2.9**
|
||||
releases were restored from the local `build/` artifacts and verified (4 and 5 assets).
|
||||
`git.allucanget.biz` runs on this host as user `zwitschi`; root@192.168.88.30 is **not**
|
||||
reachable from this workstation (publickey denied; password auth closes the banner), so host
|
||||
maintenance must go through a `zwitschi` login with sudo.
|
||||
- **Host disk full (2026-10-31, historical):** the v0.2.11 tag build created release id 27 and logged the
|
||||
uploads, but they failed with `HTTP 500 Attachments.Save: write … no space left on device`
|
||||
(`/dev/mapper/pve-vm--500--disk--0`, 7.8G, 100%, 0 free). `git push` then failed too
|
||||
("unpacker error"). Unblocked by deleting the superseded **v0.2.0** and **v0.2.9** releases
|
||||
via the API (~565 MB of assets), then publishing v0.2.11 locally with the fixed script
|
||||
(verified 5 assets). Remaining ~150 MB is tight — the next release needs host cleanup on
|
||||
192.168.88.30: `sudo journalctl --vacuum-size=100M` (journal 837 MB), `sudo apt-get clean`
|
||||
(91 MB), clear `/var/lib/gitea/data/attachments/tmp`. SSH works as `zwitschi`; `sudo -n`
|
||||
needs a password. **Releases now:** v0.2.11, v0.2.10, v0.2.1 (5 assets each); v0.2.0 and
|
||||
v0.2.9 entries were pruned. PLAN.md + ADRs 0007–0010 still describe the intended UX.
|
||||
- **Release v0.2.1 is LIVE** (2026-10-31): CI run 1110 built the NSIS installer + AppImage
|
||||
and published — installers/AppImage are on the releases page. Releasing = bump version → tag → push.
|
||||
- NSIS gotchas (validated in a local ubuntu:24.04 docker repro): makensis uses `-D` defines
|
||||
(never `/D`), resolves relative `-DICON`/`-DARTIFACT` against the **script directory** so CI
|
||||
passes `$PWD`-absolute paths; installed exe must be space-free (`gig-line.exe`); ICO must be
|
||||
classic BMP entries (ImageMagick `icon:auto-resize=48,32,16`; PIL emits PNG-compressed ICOs NSIS
|
||||
cannot load).
|
||||
- appimagetool runs system `appstreamcli` if present (strict, exit 3 on warnings) — project
|
||||
metadata now passes `--pedantic`.
|
||||
- Distribution facts (verified via Gitea API + host creds): repo is **public**, account is
|
||||
admin; `v0.2.1` has all 5 assets; the installers were initially missing from the release
|
||||
because `publish_release.sh` uploaded only portables — now fixed (uploads installer +
|
||||
AppImage; falls back to `~/.git-credentials` when no `GITEA_TOKEN`).
|
||||
- Host sign-in: instance `[service] REQUIRE_SIGNIN_VIEW=true` blocks anonymous browse/download
|
||||
(not a repo/API setting). `~/.git-credentials` holds the Gitea creds (basic-auth usable);
|
||||
`.env` only holds `OPENROUTER_API_KEY`.
|
||||
- **Release v0.2.0 was LIVE**: pushing the `v0.2.0` tag made Gitea Actions run the build job
|
||||
and publish automatically (host has the `GITEA_TOKEN` secret). Tag page and `/releases` are
|
||||
public (HTTP 200). Caveat: anonymous **asset downloads** redirect to login on this host
|
||||
(host setting — tracked in TODO). Future releases = bump `config/version`, `git tag vX.Y.Z`,
|
||||
push. No local token needed.
|
||||
- Core simulation, three scenarios, v0.2.1 installers/AppImage, first-person prototype (inspect/wand/radio, shift review, Tab targeting, V venue cycle) are on local `main`. Tagged v0.2.1 was 57 tests; this working tree is **74/74**.
|
||||
- NPC avatars now walk to per-intent waypoints (`VenueScenario.waypoint_for_intent`): enter/trespass/deliver/arrive → entrance, photo/film/perform/wander/roam → sidewalk, sit → steps_end. `NpcAvatar.setup` takes an explicit `door_position`.
|
||||
- Returning-trespasser behavior: `NpcProfile.return_probability` (trespasser 0.6, mental-crisis 0.4, others 0). `NPCController._maybe_schedule_return()` moves a directed-away NPC to `LOITERING_DISTANT`; `tick_return(delta)` fires after 20-60s and transitions back to `APPROACH`. `NpcAvatar._advance_fsm` re-targets the door on return.
|
||||
- Perception pass: `NPCController.perceive_guard(distance)` returns `{noticed, band, reaction}` with bands `unaware` (>6m), `aware` (2-6m), `near` (<2m). Near + temperament ≥0.6 → ARGUE, else COMPLY. `GameManager._perception_pass()` feeds real guard distance each physics tick and logs first-time notice.
|
||||
- Responder dispatch: `RadioSystem` tracks `pending_calls` and decrements `arrival_delay_s` in `tick`; emits `responder_arrived` when a call's delay elapses. `RadioSystem.responder_outcome(call)` maps danger/weapons → command/restraint/evacuation/arrest. `GameManager._on_responder_arrived` spawns a `RESPONDER` avatar at the entrance and logs the outcome. HUD shows an emergency-radio cooldown label via `_update_radio_cooldown`.
|
||||
- Wand screening (Joke Shop): `WandScreener` classifies carried items as prohibited vs false-alarm vs clear. `NpcProfile.carried_items` is filled at spawn on wand venues. `GameManager` binds **`C`** to `_toggle_wand_screening` and `A`/`D` to `decide_wand` while inspect/wand is open. `decide_access` still blocks admission until `controller.screened` is true, and `verify` now takes that flag. Door-staff lines log through GameManager.
|
||||
- Design change (2026-10-31): the selectable dialogue tone-of-voice system was **removed**.
|
||||
`Rules.Tone`/`TONE_NAMES`, `NpcProfile.tone_modifiers`, and the tone param on
|
||||
`NPCController.roll_reaction/apply_player_action` no longer exist; `NpcProfile.dialogue_lines`
|
||||
is a flat `Array[String]`; `GameManager` uses `_apply_interaction(level, action_name)`;
|
||||
the `T` key is gone. HUD `tone_label` removed and `hud.tone_prefix` string deleted from JSON.
|
||||
- Polish/accessibility: venue cycling (`V` cycles `VENUE_ORDER` and restarts the shift), aim-free
|
||||
targeting (`Tab` cycles nearby NPCs within `NEARBY_RADIUS`), color-coded escalation indicator
|
||||
(`Hud.set_escalation`), procedural audio (`AudioFeedback` node generates sine tones for
|
||||
confirm/radio/escalation/deny), and a centralized `Strings` table (`src/core/strings.gd`, `lookup`/`fmt`)
|
||||
backed by `content/strings.en.json`. NOTE: `Strings.get` was renamed to `Strings.lookup` because
|
||||
`get` collides with `Object.get`.
|
||||
- Newly added: LATE shift phase (start 700s) with a reachability test; PROTESTER archetype + events + colors; CALL_SOMEONE reaction; `DoorState` core class + `L` binding + visual indicator; flashlight (`G`, SpotLight3D, perception bonus at evening/late); persistence wired (`Save.save_shift_result` on shift end, history + best score on the review screen); `NavigationRegion3D` flat navmesh + `NavigationAgent3D` NPC paths (steps now visual-only); strings moved to `res://content/strings.en.json` (41 keys, loader keeps `lookup`/`fmt`); `export_presets.cfg` (Linux/Windows/macOS) + `tools/build_releases.sh` + `docs/RELEASING.md`; README Downloads section.
|
||||
- Run tests: `godot --headless --path . --script res://src/test/test_runner.gd`
|
||||
- Boot check: `godot --headless --path . --quit-after 120`
|
||||
- Screenshot tools: `godot --path . -s res://src/test/take_screenshot.gd` (single shot to `user://screenshot.png`); `godot --path . -s res://src/test/capture_screenshots.gd` (staged README gallery shots to `user://screenshots/`, copied to `docs/screenshots/`).
|
||||
- Release build verified locally: export templates for 4.3 installed at `~/.local/share/godot/export_templates/4.3.stable/`; `./tools/build_releases.sh` produced Linux/Windows/macOS artifacts for 0.1.0 in `build/` and the Linux binary passed a headless smoke test. Publishing to Gitea still needs a token (see `tools/publish_release.sh`, `docs/RELEASING.md`).
|
||||
- CI: the repo now carries a **Gitea Actions** workflow at `.gitea/workflows/build.yml` (checks on
|
||||
push/PR; full export + artifact upload + optional release publish on tags and `workflow_dispatch`).
|
||||
A repo secret `GITEA_TOKEN` is required for tag releases. The old `.github/workflows/ci.yml` was
|
||||
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 door-staff dialogue: `VenueScenario.door_staff_line(context, rng)` and GameManager logging
|
||||
(id/ticket/wand-check, refusal, prohibited-item) — Joke Shop flavor.
|
||||
- CI cache: `.gitea/workflows/build.yml` no longer uses `actions/cache` (the runner can't reach
|
||||
the GitHub cache service; import is fast). `GODOT_SILENCE_ROOT_WARNING=1` set on jobs.
|
||||
Logs live in `.gitea/logs/` after each run.
|
||||
- Visual polish (v0.2.0): phase lighting (GameManager.PHASE_LIGHTING + ProceduralSkyMaterial),
|
||||
street lamps + night mode in WorldBuilder.set_night_mode, marquee Label3D, crosswalk/planters/
|
||||
trees, NPC per-seed variation. capture_screenshots.gd shoots day + fast-forwarded LATE night
|
||||
(6 shots in docs/screenshots/). Version bumped to 0.2.0; binaries rebuilt.
|
||||
- Assets pipeline: `python3 tools/generate_textures.py` produces all authored PNGs under
|
||||
`assets/textures/` (≤1024px, pow2); regenerate after changing the script, then run
|
||||
`godot --headless --import` before tests. 57 headless tests guard the pipeline.
|
||||
- General image pipeline: `python3 tools/asset_pipeline.py` defaults to
|
||||
`openai/gpt-5-image-mini`, requests supported 1:1/3:2 sources, then fits each role's target.
|
||||
It provides crop, resize, tile edge blending, alpha-glyph recoloring, and role-aware audits.
|
||||
Screenshot auditing checks 1280x720 minimum, 16:9 aspect, and contrast; visual composition
|
||||
remains a manual check. Python tools require Pillow from `requirements-tools.txt`.
|
||||
- AI texture mode: `python3 tools/generate_textures.py --ai` generates textures via the
|
||||
OpenRouter image API (`POST https://openrouter.ai/api/v1/images`, model
|
||||
`bytedance-seed/seedream-4.5` by default, base64 PNG in `data[].b64_json`). Requires
|
||||
`OPENROUTER_API_KEY` (a gitignored `.env` in the repo root holds it locally; source it
|
||||
with `set -a && . ./.env && set +a`). `--prompt`/`--name`/`--model`/`--size` override the
|
||||
default batch. Output is downscaled to ≤1024px. NOTE: seedream rejects sizes below
|
||||
3,686,400 px (1920×1920), so the script requests `GEN_SIZE=2048` and downscales — do not
|
||||
lower `GEN_SIZE` below 1920. Verified end-to-end 2026-10-31 (3 textures generated).
|
||||
Legacy `--ai` remains on Seedream 4.5 and still ignores `background: transparent`; do not
|
||||
use it for alpha-dependent assets. Gemini 3.1 Flash Image advertised broad ratios but the
|
||||
current provider rejected authentication. The validated role-aware model is GPT-5 Image Mini.
|
||||
- Runtime assets: all 15 loaded world/HUD/card textures now live in
|
||||
`assets/textures/ai/game_ready/`; each passes role audit. Generated tile edges are blended,
|
||||
icon alpha masks are recolored white where needed, and the vignette uses the existing
|
||||
deterministic generator because the API returned an empty transparent image. The six
|
||||
`docs/screenshots/` captures were regenerated after wiring and pass screenshot audit.
|
||||
- Detailed graphics: textured facade/awning/skyline, humanoid NPC rigs + props + walk bob +
|
||||
attention head-turn, inspection artifact cards, mode icons, escalation vignette, body-cam
|
||||
overlay (REC + shift clock). Screenshot set regenerated (`docs/screenshots/`, day + night).
|
||||
- Polish: wand scan cooldown (`WAND_COOLDOWN_S` + `wand.recharge` message).
|
||||
- NOTE: collaborators occasionally add uncommitted work to this shared tree (e.g. the radio
|
||||
easter egg); check `git status` before committing and leave foreign changes untouched unless asked.
|
||||
|
||||
- Screen-design references (`tools/screen_designs.py`, tests in `tools/test_screen_designs.py`):
|
||||
`generate` turns the screen descriptions in `docs/` (README captions, ADRs 0007–0010,
|
||||
PLAN, architecture) into reference images under `docs/screens/designs/` — `--draft`
|
||||
draws offline Pillow wireframes, `--ai` generates realistic 3D scenes / in-game UI via
|
||||
OpenRouter (default model `openai/gpt-5-image-mini`). Screens are tagged `scene` or
|
||||
`ui`; prompts get a shared art-direction suffix, and requests use a 3:2 landscape
|
||||
source cropped to 16:9 (not a square centre-crop). `compare` checks `docs/screenshots/*.png` for aspect/size/
|
||||
blank-frame and coarse layout/palette/detail drift. Drift distances are warnings, not
|
||||
failures: a mockup never matches a real render, so semantic composition is a follow-up
|
||||
vision-model judge (tracked in TODO).
|
||||
|
||||
## Known Issues / Notes
|
||||
|
||||
- Reference cycles are fixed: `FSM` holds its owner via `WeakRef`, `ShiftCoordinator` is
|
||||
`RefCounted`, and persistence tests free the `Save` node — the suite exits with no
|
||||
"resources still in use" warnings (was ~13). If a future subsystem introduces a cycle,
|
||||
repeat the WeakRef pattern.
|
||||
- Shift length: `GameManager` uses the scenario's `shift_duration_s` (900s ≈ 10-15 min);
|
||||
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.
|
||||
- Steps are visual-only (no collider) so NPCs can path to the entrance; the guard spawns on the landing so it never needs to climb.
|
||||
- Persistence tests write into the real `user://shift_results.json` (harmless local pollution of the demo history).
|
||||
- macOS export required `rendering/textures/vram_compression/import_etc2_astc=true` in `project.godot` (universal build demands ETC2 ASTC enabled).
|
||||
- Joke Shop `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI.
|
||||
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
|
||||
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
|
||||
- Headless suite is **229/229**; the windowed smokes, screenshot compare and judge pass. Remaining visual follow-ups are in `TODO.md`.
|
||||
- Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args).
|
||||
- GDScript inference trap that broke the main scene: a `var` typed as a base class
|
||||
(`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a
|
||||
parse error. Type it as the script type (`ActionRadial`) or annotate the local explicitly.
|
||||
`--check-only --script` cannot see the `Save` autoload; use a real boot (`--quit-after 180`) to
|
||||
verify a full-scene compile.
|
||||
- Do not snapshot right after changing a transform in the same frame: `capture_screenshots.gd`
|
||||
- **Two character builders — do not confuse them.** `tools/blender_assets.py` is the real builder
|
||||
for the **eight library characters, 18 gear pieces and props** (it has the 0.9.4 face/hand
|
||||
sculpting in `_face()`/`_hands_and_shoes()`); `tools/npc_pipeline.py` builds **only**
|
||||
`assets/models/generated/npc_base.glb`, the frozen plain-primitive fallback. Reading
|
||||
`npc_pipeline.py` to judge the library's face/hand detail is wrong.
|
||||
- **Pose capture fixed (2026-11-04):** the harness was missing `set_physics_process(false)`/
|
||||
`set_process(false)` on the avatar, so the FSM (`_advance_fsm` + `_move_toward_target`) ran every
|
||||
tick, reset `arrived`/velocity and made `theater_pose()` revert to idle/rest. Disabling both fixes
|
||||
it (sit vs idle diff 5.63, walk vs idle 2.29). The earlier "moving the `Rig` child does not
|
||||
render" probe was a red herring — it set the position inside `_shoot()` in the same frame as
|
||||
`root.get_texture().get_image()`, so it never rendered a frame. Lesson: pose/portrait harnesses
|
||||
must freeze the avatar's process/physics before driving `_animate()`.
|
||||
- Capture/pose harness gotchas: scripts that read captured-mouse input must run windowed
|
||||
(`smoke_props.gd`); the NPC FSM rewrites `rotation.y` every physics tick, so pose/portrait
|
||||
captures set the yaw after `set_physics_process(false)`; `visible=false` does not keep the crowd
|
||||
out of a portrait frame (park the rest of the crowd away instead).
|
||||
- Blender: `bisect_plane` and thin bevels leave zero-area triangles (Godot "non-finite normal in
|
||||
LOD generation"); `_clean()` + a weld after bisect fixes them and the audit rejects them.
|
||||
- Quaternius arm-down bake (working recipe, `tmp/npc-research/bake_armdown_v5.py`): pose-rotate
|
||||
`UpperArm.L/R` −90° local X → read the pose matrices → apply the Armature modifier → **reset the
|
||||
pose to identity** → set each arm bone's rest matrix from its pose matrix → re-add the modifier →
|
||||
export `export_apply=False`. Missing the pose reset double-applies the arm-down (mesh ±0.53
|
||||
instead of ±0.23). `armature_apply` in Blender 5.2 is broken (deforms the mesh but does not bake
|
||||
the rest). The canonical base must be **join-only** (`build_base_minimal.py`: no `remove_doubles`
|
||||
weld, no scale/ground transform, keep the Armature modifiers) — the weld and the scale/ground
|
||||
transform corrupt the skin weights (arm poses to ±0.51 instead of −0.23).
|
||||
- Quaternius `NpcAvatar` wiring (commits `2a6f29f`, `cd285b3`): `RIG_BONES` +
|
||||
`NpcAvatar.bone_name(joint, side)` hold the **only** side mapping — the Quaternius labels `L`/`R`
|
||||
opposite the project, so proxy side 0 (`-x`) drives `UpperLeg.R`/`UpperArm.R` while side 1 drives
|
||||
the `.L` bones. The arm/elbow swing axis is `Vector3.LEFT` (legs stay `RIGHT`). The hand proxy is
|
||||
`global_wrist − global_elbow` (subtracting the parent-relative `elbow.position` misplaces
|
||||
handheld gear). Old 13-core names resolve through the same table.
|
||||
- **Bone posing must write the global pose** (`NpcAvatar._swing_bone`): `reset_bone_pose()` then
|
||||
`set_bone_global_pose(Transform3D(Basis(swing * global_rot), current_origin))`, conjugated with
|
||||
`get_bone_global_rest()`. Both rejected alternatives fail: the rest-relative
|
||||
`set_bone_pose_rotation(rest * Quaternion(rest⁻¹·axis, angle))` swings about the parent-relative
|
||||
rest (the glTF importer already folded the axis conversion into it) and splayed the Quaternius
|
||||
arms sideways; the plain `rest⁻¹ * swing * rest` conjugation flips the limbs 180°. Verified by
|
||||
measuring `get_bone_global_pose()` after the call — posed bone axis matches the proxy pivot at
|
||||
dot 1.000 on the Quaternius and the procedural rigs render unchanged.
|
||||
Two caveats: children need the parent's swing passed in (the write is absolute), and
|
||||
`set_bone_global_pose()` is a **no-op outside the scene tree**, so the headless runner (tests run
|
||||
in `_initialize()`) cannot assert on it.
|
||||
- **The Quaternius source UV map is degenerate** — every loop at (0,0), flat-colour materials, no
|
||||
atlas. The AO/texture bake silently produces a 100 % black map, which tints every AO'd slot
|
||||
(skin, hair, accessory) black in Godot: a black leg is the symptom. `build_base_minimal.py` now
|
||||
Smart-UV-unwraps and repacks the layout (texel density 0 → 73–74 px/m on 256²), and the base
|
||||
pipeline is two explicit steps with distinct files: `build_base_minimal.py`
|
||||
(`quaternius/Casual_Hoodie.glb` → `quaternius_tpose.glb`, joins without welding, renames the
|
||||
materials to the five tint slots, unwraps) then `bake_armdown_v5.py` (`quaternius_tpose.glb` →
|
||||
`quaternius_base.glb` + `assets/models/blender_src/npc_casual.blend`). Re-running v5 on its own
|
||||
output double-bakes the rest.
|
||||
- Hand gear must be self-contained: the camera carried a 0.42 m neck-strap loop that floats when the
|
||||
item is parented to the wrist. Hand items get a short wrist strap, not a neck strap. The backpack
|
||||
straps were authored for the wider procedural chest and floated ~5 cm proud of the Quaternius
|
||||
torso; the chest points now sit at x 0.12 / Godot z 0.15, which fits both.
|
||||
- Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not
|
||||
lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept
|
||||
generation defaults to `--request-size 1536`.
|
||||
- Local photo research tooling: DDG rate-limits by IP (HTTP 202) — the `refcollect.py` token bucket
|
||||
(0.5 req/s, burst 3) plus retry/backoff handles it; some DDG frames are AVIF mislabeled
|
||||
`.png`/`.jpg` (convert with ffmpeg; `refcollect.py sheet` skips unreadable). Commons API needs a
|
||||
descriptive User-Agent and rate-limits thumbnail downloads (fetch all metadata in one
|
||||
`titles=A|B|C` query).
|
||||
- Frozen decisions: do not re-open `override_access`; do not bind wand to `W`; do not remove the
|
||||
radio easter egg.
|
||||
- GDScript: no static signals; lambdas capture locals **by value** (mutating a captured local does
|
||||
not affect the outer variable — use member functions for accumulators).
|
||||
- Reference cycles: `FSM` holds its owner via `WeakRef`, `ShiftCoordinator` is `RefCounted`; if a
|
||||
new subsystem introduces a cycle, repeat the WeakRef pattern.
|
||||
- Headless (dummy renderer) logs `Parameter m is null` from `mesh_get_surface_count` — benign, does
|
||||
not occur with a real GL driver.
|
||||
- macOS export requires `rendering/textures/vram_compression/import_etc2_astc=true` (universal
|
||||
build demands ETC2 ASTC).
|
||||
- Typed spawn signal: handlers must accept the 4th `event: Dictionary` arg or Godot will not call
|
||||
them.
|
||||
- GDScript inference trap: a `var` typed as a base class (`radial: CanvasLayer`) exposes script
|
||||
members as `Variant`; type it as the script type (`ActionRadial`). `--check-only --script`
|
||||
cannot see the `Save` autoload — use a real boot (`--quit-after 180`) to verify full-scene
|
||||
compile.
|
||||
- Do not snapshot right after changing a transform in the same frame — `capture_screenshots.gd`
|
||||
needs a state/frame gap or the capture uses the old camera orientation.
|
||||
- `EventScheduler` probabilities are per simulated second, not per call. Never poll it per frame.
|
||||
- Screenshot staging lives in `src/test/capture_screenshots.gd` (guard at z≈10.5 facing the door).
|
||||
`docs/screenshots/` copies are manual: copy from `user://screenshots/`.
|
||||
- `EventScheduler` probabilities are per simulated second, not per call; never poll it per frame.
|
||||
- Light falloff: `omni_attenuation = 0.0` is **constant**, not physical, falloff in Godot 4.7 —
|
||||
all local lights use `1.0`.
|
||||
- glTF `baseColorFactor` is LINEAR — writing sRGB makes props pastel; `srgb_to_linear()` is applied
|
||||
in `build_glb`. A new/changed `.glb` needs `godot --headless --import` before `load()` sees it.
|
||||
- **Rebuilding a character GLB also requires re-baking its AO** (`tools/bake_ao.py -- --only
|
||||
<name>`). `blender_assets.py build` writes the GLB only; the old `<name>_ao.png` then maps onto
|
||||
the new geometry and produces large dark cheek/jaw blotches. Bake the AO, re-import, and refresh
|
||||
the stale `.godot` cache (scratch-copy import) before judging a face/body change.
|
||||
- `Save.SETTINGS_FILE` is declared and unused; settings persist through
|
||||
`load_settings()`/`save_settings()`.
|
||||
- Crowd reactions emit `event_bus.crowd_changed`; `GameManager._connect_crowd()` must reconnect
|
||||
after every `coordinator.setup()` (same class of bug as the radio-dispatch reconnect).
|
||||
- Packaging: NSIS uses `-D` defines (never `/D`) and resolves relative defines against the script
|
||||
directory (pass `$PWD`-absolute paths); installed exe must be space-free; ICO must be classic
|
||||
BMP entries (PIL emits PNG-compressed ICOs NSIS can't load). appimagetool runs system
|
||||
`appstreamcli` — pass `--pedantic`.
|
||||
- Credentials: `~/.git-credentials` holds the Gitea creds; `.env` holds only `OPENROUTER_API_KEY`.
|
||||
- `assets/textures/game_ready/` is not imported (no `.import`) — use the imported
|
||||
`assets/textures/skyline.png` path.
|
||||
- Godot 4.7 writes `*.gd.uid` files — kept out of `git status` via `.git/info/exclude`.
|
||||
- Collaborators occasionally leave uncommitted work in the shared tree; check `git status` before
|
||||
committing and leave foreign changes untouched.
|
||||
|
||||
## Commands
|
||||
|
||||
- Tests: `godot --headless --path . --script res://src/test/test_runner.gd`
|
||||
- Boot check: `godot --headless --path . --quit-after 120`
|
||||
- Single screenshot: `godot --path . -s res://src/test/take_screenshot.gd`
|
||||
- Staged gallery: `tools/check_screenshots.sh` (re-run the whole script, never a partial capture)
|
||||
- Blender build: `flatpak run org.blender.Blender --background --python tools/npc_pipeline.py -- build`
|
||||
(flatpak cwd is `/app/blender` and cannot read `/tmp` — use absolute repo paths; `.gdignore`
|
||||
markers keep Godot from importing `.blend`/`tmp`)
|
||||
- Textures: `python3 tools/generate_textures.py` (then `godot --headless --import`); AI mode
|
||||
`python3 tools/generate_textures.py --ai`; general pipeline `python3 tools/asset_pipeline.py`
|
||||
- Character checks: `python3 tools/blender_assets.py inspect|audit|fit`,
|
||||
`python3 tools/npc_proportions.py`
|
||||
- Docs: `python3 tools/check_docs.py` (every tracked image must be linked from its folder README)
|
||||
- Head/part pipeline: `python3 tools/npc_parts.py build|extract|assemble|inspect|sheet|compare`
|
||||
(Blender subcommands run under flatpak). Head sources: `build_character(head="face"|"proto"|"mh")`.
|
||||
- Reference research: `python3 tools/refcollect.py search <q> --slug <block> --limit N`
|
||||
(local-only), `tools/refboard.py` (Commons), `tools/three_up.py compose/rows`,
|
||||
`tools/scene_concepts.py generate/anchor/realism`
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Whether the responder avatar should be playable/controllable or purely scripted.
|
||||
- Localization target languages beyond English.
|
||||
- macOS notarization approach for broad distribution.
|
||||
- Quaternius scope: full 12-role swap, crowd-only, or extra head detail (owner decision at 0.9.9).
|
||||
- Localization target languages beyond English (0.11.0/0.12.0).
|
||||
- macOS notarization approach for broad distribution (0.12.0).
|
||||
- Fourth-venue choice for 0.11.0 (candidate: TCL Chinese Theatre forecourt).
|
||||
|
||||
## Plan vs code
|
||||
## Current State
|
||||
|
||||
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
|
||||
2026-11-01 the playable build covers all three venues, controls, access/radio/evidence/review,
|
||||
the Blender asset library, the realism pass, and the visual-alignment pass. `TODO.md` holds only
|
||||
the realism follow-ups and optional future ideas. The project uses a custom headless runner, not
|
||||
GUT, and Gitea Actions, not GitHub Actions.
|
||||
v0.9.8 is published. The build covers the three venues, the shift loop, tutorial/campaign/
|
||||
checkpoints, the 0.9.3–0.9.8 polish/texture/integration/character passes and the Blender +
|
||||
realism asset pipeline (see `DONE.md` and `CHANGELOG.md`). The 12-character procedural library
|
||||
(realistic face + four crowd variants) and the Comedy Store map rebuild are complete.
|
||||
|
||||
**Planning pivot (2026-11-05, owner):** the character and map work was re-sequenced into
|
||||
patches (`0.9.9`–`0.9.12`) with `0.10.0` as the public-readiness minor. The gap analysis and the
|
||||
three release roadmaps now live in `PLAN.md`; `TODO.md` is the executable task list. ADR-0017
|
||||
Stage 1 (proportion warp) is dead; ADR-0018 option 2 (62-joint runtime) is accepted.
|
||||
|
||||
**Quaternius pilot (2026-11-05, commits `cd285b3`, `fb3e75a`, `90f189a`):** `npc_casual` ships as
|
||||
the Quaternius base. It renders arm-down, carries the five tint slots, plays the Idle/Walk/Sit pose
|
||||
code on the 62-joint rig, and the procedural rigs are visually unchanged. Verified: `inspect`
|
||||
(no issues), `audit` OK (6,206 tris / 12,988 verts, 62 bones), `fit` head 0.201 m, texel density
|
||||
73–74 px/m, `npc_proportions` measured, 24-NPC budget smoke OK (168 meshes, 162,304 tris,
|
||||
0.82 ms avg frame). The face/silhouette gate sheet and the three options are in
|
||||
`docs/screens/npc-face-gate/`; the **owner decision is the only thing left before the 0.9.9
|
||||
rollout**.
|
||||
|
||||
Test counts as of 2026-11-05: Python 167/167, Godot 278/278, `check_docs` OK.
|
||||
|
||||
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|
||||
|
||||
## Uncommitted Workstreams
|
||||
|
||||
- **None (2026-11-01).** The NPC detail pass, the realism follow-ups and the flashlight beam
|
||||
decision are committed together with the portrait capture and the refreshed screenshots.
|
||||
Keep the rule from `AGENTS.md`: check `git status` before committing, and never revert
|
||||
another contributor's in-flight files.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -14,6 +14,94 @@ 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.
|
||||
- **Readability beats raw fidelity.** The GL Compatibility renderer, clear silhouettes, the focus ring, and the HUD stay legible.
|
||||
|
||||
## Development Priorities
|
||||
|
||||
The active task order is in [`TODO.md`](TODO.md); the gap analysis and the release roadmap are
|
||||
below. The current order is:
|
||||
|
||||
1. **0.9.9–0.9.12 patches.** Finish the character base (the accepted Quaternius 62-joint runtime),
|
||||
roll it out to the 12 roles, close the scheduled venue/prop/lighting polish, and verify
|
||||
performance, Web and accessibility.
|
||||
2. **0.10.0 minor.** Public readiness: external playtests, balance acceptance, legal/storefront
|
||||
approval, the platform matrix and the documentation/picture lock.
|
||||
3. **0.10.x patches → 0.11.0.** Content and systems depth: live body-camera replay, a fourth
|
||||
venue and the localization foundation.
|
||||
4. **0.12.0 → 1.0.0.** Platform/distribution, content complete, release candidates and the stable
|
||||
release.
|
||||
|
||||
Versions are assigned only when a track's scope is accepted into a release (see
|
||||
[Release Roadmap](#release-roadmap)). Unversioned tracks — the 3D review evidence base,
|
||||
maintenance and the Gaussian-splatting study — do not receive a placeholder version.
|
||||
|
||||
## Plan Evaluation (2026-11-05)
|
||||
|
||||
The game plan itself is sound and largely built: three venues, the escalation ladder, access
|
||||
artifacts, the radio/report system, the shift review and the campaign loop all exist and are
|
||||
tested. What needed fixing was the **delivery plan**, not the design.
|
||||
|
||||
- **Character sourcing churned.** The project moved procedural → MakeHuman → Quaternius, and the
|
||||
Quaternius Stage-1 warp failed its own gate. That leaves a correct decision (ADR-0018) with a
|
||||
half-finished implementation. The plan now makes the rig correction one small version, gates the
|
||||
roster behind a face/readability decision, and keeps exactly one fallback.
|
||||
- **One milestone carried too much.** The old 0.10.0 mixed character work, map work and every
|
||||
public-release gate, so one blocked export blocked the release. Character and venue polish are
|
||||
now patches; 0.10.0 is only the release gate.
|
||||
- **Unversioned work had no deadline.** The 3D review backlog could grow without bound. It is now
|
||||
capped: the ranked venue/prop/lighting items are scheduled into 0.9.11, the budget checks into
|
||||
0.9.12, and everything else is an explicit accept or deferred. Gaussian splatting stays
|
||||
unscheduled because no product need justifies its risk yet.
|
||||
- **Legal, signing and storefront work is release work, not polish.** It now has named checkboxes
|
||||
in 0.10.0 and 1.0.0 instead of living in prose.
|
||||
- **What is deliberately not planned:** new genres or mechanics, a live-service model, or a
|
||||
runtime splat renderer. The de-escalation-first, rare-emergency design stays frozen.
|
||||
|
||||
Feasibility: the remaining character work is small in geometry terms (6.2k triangles against a
|
||||
24k cap) and larger in rig/export terms; the venue work is polish; most release gates are
|
||||
evidence gathering rather than engineering. The main schedule risk is external review and
|
||||
approval, not code.
|
||||
|
||||
## Character Creation Re-Evaluation (2026-11-05)
|
||||
|
||||
**Where it stands.** The shipped library is 12 procedural Blender variants (4.7k–5.8k triangles
|
||||
each) with a sculpted face, five tint slots and 18 gear pieces. The owner then chose the CC0
|
||||
**Quaternius Ultimate Modular Characters** as the primary source. Stage 1 (warp the Quaternius
|
||||
mesh onto the 13-core contract) failed its silhouette gate — arm ×1.63, leg ×0.74, torso ×1.61,
|
||||
judged distorted. [ADR-0018](docs/adr/0018-quaternius-stage2-runtime-rig.md) therefore adopts the
|
||||
source **62-joint rig at runtime**: no warp, source LOD (3,199 verts / 6,206 tris), source
|
||||
Idle/Walk clips, and gear pivots re-derived from the Quaternius skeleton.
|
||||
|
||||
**What is already built.** The canonical `quaternius_base.glb` (welded, grounded, arm-down rest),
|
||||
the 62→13 joint map, the material→five-slot map, the clip inventory (24 clips; Idle and Walk in
|
||||
scope) and the `NpcAvatar` joint-name mapping with rest-derived gear pivots.
|
||||
|
||||
**What actually blocks it.** Two technical items about the export and the pose math, not the art:
|
||||
|
||||
1. the arm-down GLB renders in A-pose in Godot because the bake writes wrong inverse bind
|
||||
matrices; and
|
||||
2. `_pose_bone()` composes rotations as `rest * Quaternion(axis, angle)`, which only cancels for
|
||||
the old 180° rest, not the Quaternius ~76° rest.
|
||||
|
||||
**Re-evaluation findings.**
|
||||
|
||||
- Triangle budget is not the problem (6.2k against a 24k cap). Do not decimate the source.
|
||||
- **Face fidelity is the real risk.** The 0.9.7 library has a sculpted realistic face; the
|
||||
Quaternius head is low-poly. A blanket swap would regress every close-up. The plan therefore
|
||||
makes the owner choose at 0.9.9 between (a) a full swap, (b) Quaternius for crowd roles with the
|
||||
procedural library for named roles, or (c) extra head subdivision/hair cards on the Quaternius
|
||||
head.
|
||||
- **Animation** becomes clip-driven, which removes the hand-built pose composition as the main
|
||||
path. Poses the clips do not cover (seated, raise-hand, head turn) must be decided explicitly
|
||||
rather than patched per scene.
|
||||
- **One fallback, no third source.** The procedural `npc_base` and the MakeHuman head stay tested
|
||||
until the new base passes the gate. MPFB body work stays closed and the Stage-1 warp is not
|
||||
reopened. A new character source needs a new ADR.
|
||||
- **Sequence matters.** Fix the rig first (0.9.9), then the roster (0.9.10). Rolling a broken rig
|
||||
across 12 variants would waste the whole pass.
|
||||
|
||||
**Reversal condition.** If the skin cannot be exported correctly without a new exporter path, or
|
||||
if the owner rejects the low-poly face in every option, the plan falls back to the procedural
|
||||
library and retires Quaternius with a recorded ADR. That decision is made at 0.9.9, not later.
|
||||
|
||||
## Core Gameplay Loop
|
||||
|
||||
1. Watch the entrance, steps, queue, and sidewalk.
|
||||
@@ -96,15 +184,19 @@ A poor shift involves excessive force, confronting innocent people, ignoring a r
|
||||
|
||||
## 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.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -123,7 +215,7 @@ Joke Shop escalation follows the common ladder: direction, explanation, repeat r
|
||||
|
||||
### Hollywood Boulevard Late Night TV Studio
|
||||
|
||||
A busy studio entrance with audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, influencers, and the same range of street behavior as the building scenario.
|
||||
A busy studio entrance at the same real El Capitan Entertainment Centre block as the Hollywood day shift, restaged for a late-night arrival: the same stone facade and steps with a bulb-lit canopy, a black car at the kerb and a barrier line. It has audience members, production staff, performers, guests, deliveries, celebrity arrivals, tourists, influencers, and the same range of street behavior as the building scenario.
|
||||
|
||||
Staff and guests are recognized or present a wristband, credential, ticket, guest-list confirmation, or other approved marker. The player keeps the steps clear, protects arrivals and equipment, manages filming boundaries, watches for tailgating, and calls for help only when there is immediate danger.
|
||||
|
||||
@@ -148,18 +240,19 @@ The action wheel prioritizes de-escalation: Stop, Look, Point, No, Ask, Inspect,
|
||||
|
||||
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.
|
||||
- **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.
|
||||
- **Character direction:** 12 skinned NPC variants, 18 gear pieces and 20 street/venue props. The active character track is the Quaternius base at runtime ([ADR-0018](docs/adr/0018-quaternius-stage2-runtime-rig.md)): the CC0 source mesh keeps its proportions on the 62-joint rig, Idle/Walk run as clips, and the procedural library stays as the tested fallback until the face/silhouette gate passes. See [Character Creation Re-Evaluation](#character-creation-re-evaluation-2026-11-05).
|
||||
- **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.
|
||||
- **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), [ADR-0017](docs/adr/0017-quaternius-npc-base.md).
|
||||
|
||||
## 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.
|
||||
- **Architecture:** venue-driven simulation; shared systems plus `Resource`-based scenario data.
|
||||
- **Navigation:** `NavigationRegion3D` / `NavigationAgent3D`.
|
||||
@@ -172,7 +265,107 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD
|
||||
|
||||
## Versioning and Delivery
|
||||
|
||||
`config/version` in `project.godot` is the single version source (currently `0.7.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/). 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.9.8`). 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.
|
||||
|
||||
## Gap Analysis
|
||||
|
||||
**Published baseline:** v0.9.8 (2026-11-04) — three playable venues, tutorial/campaign/checkpoint
|
||||
resume, the access/radio/escalation loop, shift review, the 12-character Blender library, 277
|
||||
Godot + 162 Python tests, a 21/21 screen-design judge and Compatibility/Web/Desktop HQ builds.
|
||||
|
||||
**Current source:** 0.9.8. The 0.10.0-rc.1 package built on 2026-11-03 is stale and is superseded
|
||||
by the patch sequence below; it is not a published release.
|
||||
|
||||
| Area | Remaining gap | Milestone |
|
||||
| --- | --- | --- |
|
||||
| Character base | Quaternius arm-down skin export, rest-relative pose composition, face/silhouette acceptance | 0.9.9 |
|
||||
| Character roster | 12-role mapping, modular outfits, gear fit, AO/texels, budget evidence | 0.9.10 |
|
||||
| Venue and asset polish | Ranked prop/foliage/lighting/set-dressing fixes, collider verification | 0.9.11 |
|
||||
| Performance and Web | Frame/memory/load evidence after the content changes, LOD/impostor verification | 0.9.12 |
|
||||
| Onboarding and balance | External first-time-player evidence, balance sweeps/tuning, content lock | 0.10.0 |
|
||||
| Accessibility and localization | Acceptance pass, caption coverage, pseudo-locale/length checks | 0.9.12 / 0.10.0 |
|
||||
| QA and release engineering | Full smoke matrix, clean build/checksum verification, target-matrix launches | 0.10.0 |
|
||||
| Legal, signing, storefront | macOS signing/notarization, AI-provider rights, storefront/rating approval, privacy sign-off | 0.10.0 / 0.12.0 |
|
||||
| Systems and content depth | Live incident replay, fourth venue, localization foundation | 0.11.0 |
|
||||
| Distribution | Signed storefront builds, frozen save contract, launch languages, hardware matrix | 0.12.0 |
|
||||
| Stable release | Content complete, accessibility audit, release candidates, 1.0.0 decision | 0.13.0 / 1.0.0 |
|
||||
| Future | More venues/seasonal content, image-to-mesh props, other deferred ideas | Unscheduled |
|
||||
|
||||
The 0.9.3 UI, 0.9.4 animation, 0.9.5 texture, 0.9.6 integration, 0.9.7 character development and
|
||||
0.9.8 deformation fixes are complete in [`DONE.md`](DONE.md); they are not remaining gaps. The
|
||||
2026-11-01 documentation audit is also closed.
|
||||
|
||||
## Release Roadmap
|
||||
|
||||
Every version below follows the release procedure in `AGENTS.md`. Patch versions contain fixes and
|
||||
polish; minor versions add user-facing features; the major version is the stable release with a
|
||||
compatibility promise. Each version's executable checklist is in [`TODO.md`](TODO.md).
|
||||
|
||||
### Roadmap to 0.10.0
|
||||
|
||||
The goal of this line is to finish the character and venue work and clear every public-release
|
||||
gate. The character track is versioned rather than unversioned so a blocked rig cannot hold the
|
||||
whole release.
|
||||
|
||||
| Version | Type | Scope | Exit criterion |
|
||||
| --- | --- | --- | --- |
|
||||
| 0.9.9 | patch | Quaternius pilot: correct skin export, rest-relative pose math, Idle/Walk verification, owner face/silhouette gate, one-role (or crowd-only) rollout | A Quaternius NPC renders and animates correctly and ships behind a tested procedural fallback |
|
||||
| 0.9.10 | patch | 12-role roster, modular outfits, five slots, AO/texels, gear fit, 12×6 baseline | The full roster passes contract, gear, budget and owner-baseline gates |
|
||||
| 0.9.11 | patch | Scheduled 3D review fixes: props/foliage, gear silhouette, venue shape language, lighting, set dressing, colliders | Every scheduled P1/P2 item is fixed or accepted with a measurement |
|
||||
| 0.9.12 | patch | Performance/Web/LOD/memory verification plus accessibility and pseudo-locale checks | No regression against the pre-character baselines |
|
||||
| 0.10.0 | minor | Public readiness: external playtests, balance acceptance, legal/storefront/signing decisions, platform matrix, docs and picture lock | 0.10.0 is content-locked, externally played, legally reviewed, documented and reproducibly built |
|
||||
|
||||
### Roadmap to 0.11.0
|
||||
|
||||
This line turns a stable 0.10.x into a deeper game: the incident timeline becomes a live replay, a
|
||||
fourth venue enters the campaign, and the text catalogue becomes a real localization pipeline.
|
||||
|
||||
| Version | Type | Scope | Exit criterion |
|
||||
| --- | --- | --- | --- |
|
||||
| 0.10.1 | patch | Onboarding and accessibility issues deferred from the 0.10.0 playtests | First-hour and accessibility defects closed |
|
||||
| 0.10.2 | patch | Balance/fairness sweeps, review-text and difficulty-progression tuning | Every balance-acceptance cell is in target or explicitly accepted |
|
||||
| 0.10.3 | patch | Save/checkpoint edge cases, gamepad/remap, pause/resume, small quality-of-life fixes | No known P1 stability defect remains |
|
||||
| 0.11.0 | minor | Live body-camera replay, fourth venue (candidate: TCL Chinese Theatre forecourt), localization foundation with one secondary language | The new systems ship without regressing performance, accessibility or balance |
|
||||
|
||||
### Roadmap to 1.0.0
|
||||
|
||||
This line is about distribution, contracts and completeness: signed builds on the launch stores, a
|
||||
frozen save format, finished content and release candidates.
|
||||
|
||||
| Version | Type | Scope | Exit criterion |
|
||||
| --- | --- | --- | --- |
|
||||
| 0.11.1 | patch | Replay/venue/localization fixes from wider play | No P1 defect in the 0.11.0 feature set |
|
||||
| 0.11.2 | patch | New-venue balance, campaign pacing and content readability | The 0.11.x line is stable enough to freeze its contracts |
|
||||
| 0.12.0 | minor | macOS signing/notarization, storefront packages/metadata/ratings, save-format v2 freeze, hardware matrix, launch languages | Signed, storefront-ready builds exist on every launch platform |
|
||||
| 0.12.1 | patch | Signing, installer, metadata and save-migration fixes from storefront dry runs | Every storefront build installs, launches and migrates cleanly |
|
||||
| 0.13.0 | minor | Content complete: all planned venues/events/dialogue, audio decision, final tutorial, accessibility audit, balance freeze | All planned content is implemented and frozen |
|
||||
| 0.13.1 | patch | Release candidate 1: full smoke/performance/save/accessibility matrix | A candidate passes every gate except final approval |
|
||||
| 0.13.2 | patch | Release candidate 2: verify fixes, freeze release notes/credits/privacy/known limitations | The final candidate is reproducible and documented |
|
||||
| 1.0.0 | major | Stable release: legal/storefront/rating approval, hardware/accessibility/localization sign-off, no P0/P1, publish on every platform | 1.0.0 is published with verified checksums and no open blocker |
|
||||
|
||||
## 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 and budget; CI must never make it a hard
|
||||
gate.
|
||||
- Godot engine changes can alter lighting and imports; do not combine an engine baseline move with
|
||||
a content or feature milestone.
|
||||
- Signed macOS builds, AI provenance and storefront approval are release blockers, not polish.
|
||||
- The character pipeline is the current critical path. The rig/export fix must be proven on one
|
||||
variant before the roster rollout; otherwise 0.9.10 multiplies a defect.
|
||||
- Photo-derived map assets add a 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; keep it offline,
|
||||
version-pinned and outside CI, and never make a runtime splat renderer a release dependency.
|
||||
- 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. Cap it with the scorecard ranking: P0/P1 only, one
|
||||
asset family per commit, and an explicit accept for everything else.
|
||||
- Keep the de-escalation-first rules and rare-emergency design unchanged while adding progression,
|
||||
difficulty and feedback.
|
||||
|
||||
## Design Direction
|
||||
|
||||
@@ -184,4 +377,5 @@ The game should feel like a tense workplace simulation with occasional crisis mo
|
||||
|
||||
- A rare Bob Dobbs cameo (Easter-egg appearance; placement note in `MEMORY.md`).
|
||||
- Specific unusual NPC behaviors, such as slapping their own face, using hands as scissors, or interacting with props unexpectedly.
|
||||
- Live 3D body-camera replay; additional venues or seasonal events; real voice/dialogue audio; an image-to-mesh prop pipeline.
|
||||
- A fifth venue or seasonal events after the 0.11.0 fourth venue proves the venue pipeline.
|
||||
- Real voice/dialogue audio if the 0.13.0 audio decision does not fund it; an image-to-mesh prop pipeline remains unscheduled.
|
||||
@@ -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,16 @@ The central design goal is simple:
|
||||
|
||||
> The player wins by choosing the lowest level of escalation that keeps everyone safe.
|
||||
|
||||
## Screenshots
|
||||
## Development Priorities
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  | |
|
||||
The active planning order lives in [`PLAN.md`](PLAN.md) (gap analysis and release roadmap), [`TODO.md`](TODO.md) (executable checklist) and [`AGENTS.md`](AGENTS.md):
|
||||
|
||||
*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.*
|
||||
1. **`0.9.9`–`0.9.12` patches** — finish the Quaternius NPC base on the accepted 62-joint runtime, roll it out to the 12 roles, close the scheduled venue/prop/lighting polish, then verify performance, Web and accessibility.
|
||||
2. **`0.10.0` minor** — public readiness: external playtests, balance acceptance, legal/storefront/signing decisions, the platform matrix and the documentation/picture lock.
|
||||
3. **`0.10.x` → `0.11.0`** — live incident replay, a fourth venue and the localization foundation.
|
||||
4. **`0.12.0` → `1.0.0`** — signed storefront builds, a frozen save contract, content complete, release candidates and the stable release.
|
||||
|
||||
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.
|
||||
|
||||
## ⬇️ Downloads
|
||||
|
||||
@@ -31,15 +27,24 @@ 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 |
|
||||
| **macOS** | [`gig-line-macos-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unsigned build, quarantine note below |
|
||||
| **Web (browser)** | [`gig-line-web-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · unzip and serve the folder from any static host (no special headers) |
|
||||
| **Desktop HQ** | [`gig-line-desktop-hq-<ver>.zip`](https://git.allucanget.biz/zwitschi/security-game/releases) · optional Forward+ build with volumetric fog/SDFGI/flashlight; requires a Vulkan-capable GPU/driver |
|
||||
|
||||
**All releases:** <https://git.allucanget.biz/zwitschi/security-game/releases>
|
||||
|
||||
> Downloads are public (no sign-in required) since the host's instance-wide
|
||||
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
|
||||
>
|
||||
> **Latest: v0.7.0** — distinct Joke Shop and TV Studio geometry, fitted NPC gear,
|
||||
> and the post-v0.6.0 realism/visual-alignment work (v2 skylines/textures, UI mockup
|
||||
> art, flashlight beam, HUD fixes).
|
||||
> **Latest published: v0.9.9** — the character-roster patch: all twelve NPCs now
|
||||
> use the CC0 Quaternius base with the five tint slots, role gear and baked maps.
|
||||
> Superseded: v0.9.8, the character-development patch: the
|
||||
> 12-character library, realistic face pass, four crowd-variety variants and the
|
||||
> wrist/hip/knee deformation fixes. Previous 0.9.x patches added the
|
||||
> Hollywood/Joke Shop maps, UI/accessibility polish, NPC visual optimization,
|
||||
> texture/material refinement and integration checkpoint.
|
||||
>
|
||||
> **Current source: v0.9.8** — tagged from the same commit. The 0.10.0
|
||||
> development track remains gated on the Quaternius NPC rollout, local-map work
|
||||
> and release approvals.
|
||||
> Releases are built automatically when a `vX.Y.Z` tag is pushed
|
||||
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
|
||||
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
|
||||
@@ -52,14 +57,70 @@ 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
|
||||
[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.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Every capture is rendered from the current build by [`tools/check_screenshots.sh`](tools/check_screenshots.sh)
|
||||
(21 captures, compared against the runtime art). The full gallery, including every UI state, is in
|
||||
[`docs/screenshots/`](docs/screenshots/README.md).
|
||||
|
||||
### Venues
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  | **Hollywood Boulevard — day.** The El Capitan Entertainment Centre block: six Ionic columns, Masonic parapet, event banners, eight stone steps and the Walk of Fame terrazzo. |
|
||||
|  | **Hollywood Boulevard — late.** Sconces pool on the limestone, street lamps light the boulevard and the skyline fades into night haze. |
|
||||
|  | **Night patrol.** A tight flashlight spot picks out the door and the person in front of it; movement and awareness change at night. |
|
||||
|  | **The Joke Shop.** A black Sunset Strip club under its signature-covered canopy, circular blade sign, red curtains and the dry hillside behind. |
|
||||
|  | **TV Studio arrival.** Stone facade, bulb canopy, black car and crowd barriers for a late celebrity arrival. |
|
||||
|
||||
### Interaction and systems
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  | **Focus and targeting.** The cone focus ring, name tag and target chip; `Tab`/wheel cycles nearby people without aiming. |
|
||||
|  | **Access inspection.** A venue-correct wristband check with evidence shown before the admit/refuse decision. |
|
||||
|  | **Radio report.** Security assistance with location, behaviour and what was actually seen. |
|
||||
|  | **Wand screening.** A false alarm on keys: ask the guest to identify the item before choosing a response. |
|
||||
|  | **Escalation.** Level 3 blocking with the shape meter, body camera armed and the reaction log. |
|
||||
|  | **Body-camera timeline.** Every recorded event, step by step, in the shift review. |
|
||||
|  | **Precision walk.** Half-speed close work on the steps, with the HUD state chip visible. |
|
||||
|
||||
### Shell and UI
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  | **Venue menu.** Day and night rows for Hollywood, the Joke Shop and the TV Studio, campaign continue and review links. |
|
||||
|  | **Settings.** Difficulty, volumes, HUD scale, reduce motion, captions and key/button remapping, all persisted. |
|
||||
|  | **Shift review.** Grade, policy outcome, incidents, responder results and campaign Continue. |
|
||||
|  | **Action wheel.** De-escalation first: stop, look, point, no, ask, inspect, warn, block, reason, recognize, radio. |
|
||||
|
||||
### 3D asset progress
|
||||
|
||||
The character roster is built from the CC0 Quaternius bases (ADR-0017/ADR-0018) and audited
|
||||
before use; the procedural library and the MakeHuman head stay as the tested fallback. The
|
||||
sheets below show the
|
||||
current library and will be replaced after the 12 × 6 baseline review.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  | **Character library.** Twelve role-dressed variants (4.5–6.4k triangles each) on three CC0 Quaternius bases, with five tint slots, attach-point gear and baked maps. |
|
||||
|  | **Street and venue props.** Twenty library props with colliders, navigation avoidance and colour atlases. |
|
||||
|  | **Neutral baseline.** 8 characters × 6 views (front, three-quarter, profile, back, full body, crowd) used for the 0.9.4 pass and the current re-evaluation. |
|
||||
|  | **Texture refinement.** 128 → 256 px NPC maps and a 4× fabric weave, shown before/after on the same camera. |
|
||||
|  | **Crowd variety prototypes.** Four approved looks (bun/slim, bald/broad, coat/umbrella, hood/cup) queued for the roster build. |
|
||||
|
||||
More sheets and the review criteria are in [`docs/blender_previews/`](docs/blender_previews/README.md)
|
||||
and [`docs/3d-model-review.md`](docs/3d-model-review.md).
|
||||
|
||||
## Game Overview
|
||||
|
||||
A typical shift lasts 10–15 minutes: watch the entrance and sidewalk, judge why
|
||||
each person is approaching, verify access with recognition, wristbands, IDs,
|
||||
tickets or credentials, and escalate only when necessary. The game rewards calm
|
||||
resolution over force. The full mechanics — the three venues, the six-level
|
||||
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
|
||||
[`docs/gameplay.md`](docs/gameplay.md); the design rationale lives in
|
||||
[`PLAN.md`](PLAN.md).
|
||||
@@ -74,8 +135,8 @@ decisions are recorded as ADRs in [`docs/adr/`](docs/adr/).
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Engine:** Godot 4.x · **Language:** GDScript
|
||||
- **NPCs:** Blender-authored skinned library (`assets/models/blender/`) with procedural fallbacks; lightweight FSMs on `NpcProfile`
|
||||
- **Engine:** Godot 4.7.2 · **Language:** GDScript
|
||||
- **NPCs:** CC0 Quaternius skinned library (`assets/models/blender/`, ADR-0018) with a tested procedural fallback; lightweight FSMs on `NpcProfile`
|
||||
- **World:** procedural `WorldBuilder` + generated prop models
|
||||
- **Art:** v2 skyline/surface textures in `assets/textures/ai/game_ready/v2/picks/`; UI/scene art in `assets/images/`
|
||||
- **Asset tooling:** `asset_pipeline.py`, `model_pipeline.py`, `npc_pipeline.py`, `blender_assets.py`, `scene_concepts.py`, `screen_designs.py`, `check_screenshots.sh`
|
||||
@@ -100,30 +161,56 @@ Full setup, every run/test/smoke command, and the controls table are in
|
||||
|
||||
## Project Documentation
|
||||
|
||||
- [`PLAN.md`](PLAN.md) — Game design plan and scenario details.
|
||||
- [`AGENTS.md`](AGENTS.md) — Development and collaboration workflow.
|
||||
- [`TODO.md`](TODO.md) — Active checkbox task list.
|
||||
- [`PLAN.md`](PLAN.md) — Game design plan, development priorities and scenario details.
|
||||
- [`TODO.md`](TODO.md) — Priority-ordered tasks: character and local-map development, release milestones, and future work.
|
||||
- [`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.
|
||||
- [`docs/makehuman-head-review.md`](docs/makehuman-head-review.md) — Local MakeHuman head prototype, provenance and review verdict.
|
||||
- [`AGENTS.md`](AGENTS.md) — Development, commit, build and collaboration workflow.
|
||||
- [`DONE.md`](DONE.md) — Completed task sections.
|
||||
- [`MEMORY.md`](MEMORY.md) — Persistent session notes.
|
||||
- [`CHANGELOG.md`](CHANGELOG.md) — User-facing changes by version.
|
||||
- [`docs/architecture.md`](docs/architecture.md) — arc42-style architecture documentation.
|
||||
- [`docs/gameplay.md`](docs/gameplay.md) — Gameplay systems, venues, and the escalation model.
|
||||
- [`docs/venues.md`](docs/venues.md) — Real-site references, game mapping and limitations for Hollywood, the Joke Shop and the TV Studio.
|
||||
- [`docs/asset-tooling.md`](docs/asset-tooling.md) — Asset pipelines, model tools, and screenshot gates.
|
||||
- [`tools/README.md`](tools/README.md) — Tool inventory, categories, test commands and AI-skill evaluation.
|
||||
- [`.agents/skills/gig-line-blender-asset-contract`](.agents/skills/gig-line-blender-asset-contract/SKILL.md) — tracked AI skill for Blender/NPC/gear asset work.
|
||||
- [`.agents/skills/gig-line-release-qa`](.agents/skills/gig-line-release-qa/SKILL.md) — tracked AI skill for release and visual-QA work.
|
||||
- [`docs/getting-started.md`](docs/getting-started.md) — Setup, run/test commands, and controls.
|
||||
- [`docs/development-status.md`](docs/development-status.md) — Implemented features and what's next.
|
||||
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md) — Manual smoke-test checklist.
|
||||
- [`docs/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/release-qa.md`](docs/release-qa.md) — Manual smoke checklist and release-candidate balance protocol.
|
||||
- [`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/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/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
|
||||
|
||||
v0.7.0 shipped distinct venue geometry: the Joke Shop alley and TV Studio
|
||||
entrance no longer share the Hollywood layout. It builds on v0.6.1's fitted NPC
|
||||
gear and v0.6.0's Blender asset library, baked AO and environment lift. See
|
||||
[`docs/development-status.md`](docs/development-status.md) for the full
|
||||
implemented list and [`TODO.md`](TODO.md) for what's next. Manual verification
|
||||
uses the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
|
||||
v0.9.8 is the current source version: the character-development patch with the
|
||||
12-character library, realistic face and crowd variants, pose/deformation fixes
|
||||
and save-migration/release hardening. It sits on the 0.9.6 integration
|
||||
checkpoint, 0.9.5 texture refinement, 0.9.4 NPC visual optimization, 0.9.3
|
||||
UI/accessibility polish, 0.9.2 Hollywood/Joke Shop maps and the 0.9.x engine
|
||||
baseline. The active work is the `0.9.9`–`0.9.12` patch line (Quaternius NPC
|
||||
base, roster, venue/asset polish, performance) followed by the `0.10.0`
|
||||
public-readiness minor. 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
|
||||
[`docs/release-qa.md`](docs/release-qa.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
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.15,
|
||||
0.3,
|
||||
0.141
|
||||
0.296,
|
||||
0.172
|
||||
],
|
||||
"min": [
|
||||
-0.15,
|
||||
@@ -42,33 +42,11 @@
|
||||
"materials": [
|
||||
"pack_nylon",
|
||||
"steel",
|
||||
"plastic_black"
|
||||
"denim_bag"
|
||||
],
|
||||
"path": "assets/models/blender/npc_gear/backpack.glb",
|
||||
"triangles": 316,
|
||||
"vertices": 360
|
||||
},
|
||||
"beanie": {
|
||||
"attach": "head",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.193,
|
||||
0.215,
|
||||
0.183
|
||||
],
|
||||
"min": [
|
||||
-0.193,
|
||||
0.014,
|
||||
-0.203
|
||||
]
|
||||
},
|
||||
"category": "npc_gear",
|
||||
"materials": [
|
||||
"plastic_grey"
|
||||
],
|
||||
"path": "assets/models/blender/npc_gear/beanie.glb",
|
||||
"triangles": 460,
|
||||
"vertices": 403
|
||||
"triangles": 380,
|
||||
"vertices": 410
|
||||
},
|
||||
"bench": {
|
||||
"bounds_godot": {
|
||||
@@ -96,14 +74,14 @@
|
||||
"bollard": {
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.105,
|
||||
0.125,
|
||||
0.975,
|
||||
0.105
|
||||
0.125
|
||||
],
|
||||
"min": [
|
||||
-0.105,
|
||||
-0.125,
|
||||
0,
|
||||
-0.105
|
||||
-0.125
|
||||
]
|
||||
},
|
||||
"category": "props",
|
||||
@@ -114,20 +92,20 @@
|
||||
"path": "assets/models/blender/props/bollard.glb",
|
||||
"replaces": "generated/bollard.glb",
|
||||
"triangles": 138,
|
||||
"vertices": 225
|
||||
"vertices": 221
|
||||
},
|
||||
"camera": {
|
||||
"attach": "hand",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.106,
|
||||
0.376,
|
||||
0.065,
|
||||
0.06,
|
||||
0.143
|
||||
],
|
||||
"min": [
|
||||
-0.106,
|
||||
-0.065,
|
||||
-0.053,
|
||||
-0.055
|
||||
0.005
|
||||
]
|
||||
},
|
||||
"category": "npc_gear",
|
||||
@@ -136,21 +114,21 @@
|
||||
"lens"
|
||||
],
|
||||
"path": "assets/models/blender/npc_gear/camera.glb",
|
||||
"triangles": 318,
|
||||
"vertices": 406
|
||||
"triangles": 312,
|
||||
"vertices": 422
|
||||
},
|
||||
"cap": {
|
||||
"attach": "head",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.168,
|
||||
0.209,
|
||||
0.258
|
||||
0.108,
|
||||
0.204,
|
||||
0.165
|
||||
],
|
||||
"min": [
|
||||
-0.168,
|
||||
0.037,
|
||||
-0.186
|
||||
-0.108,
|
||||
0.094,
|
||||
-0.119
|
||||
]
|
||||
},
|
||||
"category": "npc_gear",
|
||||
@@ -262,14 +240,14 @@
|
||||
"fire_hydrant": {
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.24,
|
||||
0.76,
|
||||
0.19
|
||||
0.265,
|
||||
0.815,
|
||||
0.235
|
||||
],
|
||||
"min": [
|
||||
-0.24,
|
||||
-0.265,
|
||||
0,
|
||||
-0.19
|
||||
-0.235
|
||||
]
|
||||
},
|
||||
"category": "props",
|
||||
@@ -279,21 +257,21 @@
|
||||
],
|
||||
"path": "assets/models/blender/props/fire_hydrant.glb",
|
||||
"replaces": "generated/fire_hydrant.glb",
|
||||
"triangles": 466,
|
||||
"vertices": 725
|
||||
"triangles": 610,
|
||||
"vertices": 902
|
||||
},
|
||||
"firefighter_helmet": {
|
||||
"attach": "head",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.26,
|
||||
0.245,
|
||||
0.26
|
||||
0.166,
|
||||
0.227,
|
||||
0.166
|
||||
],
|
||||
"min": [
|
||||
-0.26,
|
||||
0.035,
|
||||
-0.351
|
||||
-0.166,
|
||||
0.092,
|
||||
-0.225
|
||||
]
|
||||
},
|
||||
"category": "npc_gear",
|
||||
@@ -355,14 +333,14 @@
|
||||
"attach": "head",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.184,
|
||||
0.236,
|
||||
0.123
|
||||
0.118,
|
||||
0.221,
|
||||
0.079
|
||||
],
|
||||
"min": [
|
||||
-0.184,
|
||||
-0.146,
|
||||
-0.225
|
||||
-0.118,
|
||||
-0.024,
|
||||
-0.144
|
||||
]
|
||||
},
|
||||
"category": "npc_gear",
|
||||
@@ -439,26 +417,28 @@
|
||||
"category": "props",
|
||||
"materials": [
|
||||
"news_blue",
|
||||
"dark_metal",
|
||||
"glass",
|
||||
"paper",
|
||||
"steel"
|
||||
],
|
||||
"path": "assets/models/blender/props/newspaper_box.glb",
|
||||
"replaces": "generated/newspaper_box.glb",
|
||||
"triangles": 244,
|
||||
"vertices": 528
|
||||
"triangles": 300,
|
||||
"vertices": 648
|
||||
},
|
||||
"npc_casual": {
|
||||
"base": "hoodie",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.321,
|
||||
1.824,
|
||||
0.185
|
||||
0.228,
|
||||
1.866,
|
||||
0.287
|
||||
],
|
||||
"min": [
|
||||
-0.321,
|
||||
0,
|
||||
-0.178
|
||||
-0.228,
|
||||
-0.004,
|
||||
-0.091
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
@@ -466,11 +446,11 @@
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"eye_white",
|
||||
"accessory",
|
||||
"Eyebrows",
|
||||
"Eye",
|
||||
"hair",
|
||||
"rubber"
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_casual.glb",
|
||||
@@ -487,20 +467,21 @@
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 3974,
|
||||
"vertices": 3744
|
||||
"triangles": 6206,
|
||||
"vertices": 12988
|
||||
},
|
||||
"npc_coat": {
|
||||
"base": "hoodie",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.332,
|
||||
1.824,
|
||||
0.185
|
||||
0.228,
|
||||
1.866,
|
||||
0.287
|
||||
],
|
||||
"min": [
|
||||
-0.332,
|
||||
0,
|
||||
-0.18
|
||||
-0.228,
|
||||
-0.004,
|
||||
-0.091
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
@@ -508,11 +489,11 @@
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"eye_white",
|
||||
"accessory",
|
||||
"Eyebrows",
|
||||
"Eye",
|
||||
"hair",
|
||||
"rubber"
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_coat.glb",
|
||||
@@ -528,20 +509,185 @@
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 5026,
|
||||
"vertices": 4807
|
||||
"triangles": 6206,
|
||||
"vertices": 12988
|
||||
},
|
||||
"npc_dress": {
|
||||
"npc_crowd_a": {
|
||||
"base": "casual",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.288,
|
||||
1.825,
|
||||
0.208
|
||||
0.18,
|
||||
1.843,
|
||||
0.271
|
||||
],
|
||||
"min": [
|
||||
-0.288,
|
||||
0,
|
||||
-0.208
|
||||
-0.18,
|
||||
-0.009,
|
||||
-0.099
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
"layer": "none",
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"accessory",
|
||||
"Hair_Brown",
|
||||
"Brown",
|
||||
"hair",
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_crowd_a.glb",
|
||||
"roles": [
|
||||
"tourist",
|
||||
"guest"
|
||||
],
|
||||
"tint_slots": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 6424,
|
||||
"vertices": 13440
|
||||
},
|
||||
"npc_crowd_b": {
|
||||
"base": "casual",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.18,
|
||||
1.843,
|
||||
0.271
|
||||
],
|
||||
"min": [
|
||||
-0.18,
|
||||
-0.009,
|
||||
-0.099
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
"layer": "none",
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"accessory",
|
||||
"Hair_Brown",
|
||||
"Brown",
|
||||
"hair",
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_crowd_b.glb",
|
||||
"roles": [
|
||||
"confused_visitor",
|
||||
"guest"
|
||||
],
|
||||
"tint_slots": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 6424,
|
||||
"vertices": 13440
|
||||
},
|
||||
"npc_crowd_c": {
|
||||
"base": "hoodie",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.228,
|
||||
1.866,
|
||||
0.287
|
||||
],
|
||||
"min": [
|
||||
-0.228,
|
||||
-0.004,
|
||||
-0.091
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
"layer": "coat",
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"accessory",
|
||||
"Eyebrows",
|
||||
"Eye",
|
||||
"hair",
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_crowd_c.glb",
|
||||
"roles": [
|
||||
"intoxicated",
|
||||
"guest"
|
||||
],
|
||||
"tint_slots": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 6206,
|
||||
"vertices": 12988
|
||||
},
|
||||
"npc_crowd_d": {
|
||||
"base": "worker",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.217,
|
||||
1.826,
|
||||
0.287
|
||||
],
|
||||
"min": [
|
||||
-0.217,
|
||||
-0.004,
|
||||
-0.065
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
"layer": "hood",
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"accessory",
|
||||
"hair",
|
||||
"Moustache",
|
||||
"Eye",
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_crowd_d.glb",
|
||||
"roles": [
|
||||
"tourist",
|
||||
"guest"
|
||||
],
|
||||
"tint_slots": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 4458,
|
||||
"vertices": 9370
|
||||
},
|
||||
"npc_dress": {
|
||||
"base": "casual",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.18,
|
||||
1.843,
|
||||
0.271
|
||||
],
|
||||
"min": [
|
||||
-0.18,
|
||||
-0.009,
|
||||
-0.099
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
@@ -549,11 +695,11 @@
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"eye_white",
|
||||
"accessory",
|
||||
"Hair_Brown",
|
||||
"Brown",
|
||||
"hair",
|
||||
"rubber"
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_dress.glb",
|
||||
@@ -567,20 +713,21 @@
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 4428,
|
||||
"vertices": 4026
|
||||
"triangles": 6424,
|
||||
"vertices": 13440
|
||||
},
|
||||
"npc_firefighter": {
|
||||
"base": "worker",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.37,
|
||||
1.819,
|
||||
0.213
|
||||
0.217,
|
||||
1.826,
|
||||
0.287
|
||||
],
|
||||
"min": [
|
||||
-0.37,
|
||||
0,
|
||||
-0.213
|
||||
-0.217,
|
||||
-0.004,
|
||||
-0.065
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
@@ -588,13 +735,11 @@
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"eye_white",
|
||||
"accessory",
|
||||
"hair",
|
||||
"hivis",
|
||||
"reflective",
|
||||
"rubber"
|
||||
"Moustache",
|
||||
"Eye",
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_firefighter.glb",
|
||||
@@ -608,20 +753,21 @@
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 4848,
|
||||
"vertices": 4735
|
||||
"triangles": 4458,
|
||||
"vertices": 9370
|
||||
},
|
||||
"npc_guard": {
|
||||
"base": "worker",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.361,
|
||||
1.819,
|
||||
0.185
|
||||
0.217,
|
||||
1.826,
|
||||
0.287
|
||||
],
|
||||
"min": [
|
||||
-0.361,
|
||||
0,
|
||||
-0.172
|
||||
-0.217,
|
||||
-0.004,
|
||||
-0.065
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
@@ -629,15 +775,11 @@
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"eye_white",
|
||||
"accessory",
|
||||
"hair",
|
||||
"rubber",
|
||||
"leather",
|
||||
"steel",
|
||||
"patch",
|
||||
"plastic_black"
|
||||
"Moustache",
|
||||
"Eye",
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_guard.glb",
|
||||
@@ -652,20 +794,21 @@
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 4560,
|
||||
"vertices": 4529
|
||||
"triangles": 4458,
|
||||
"vertices": 9370
|
||||
},
|
||||
"npc_hoodie": {
|
||||
"base": "hoodie",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.326,
|
||||
1.856,
|
||||
0.185
|
||||
0.228,
|
||||
1.866,
|
||||
0.287
|
||||
],
|
||||
"min": [
|
||||
-0.326,
|
||||
0,
|
||||
-0.225
|
||||
-0.228,
|
||||
-0.004,
|
||||
-0.091
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
@@ -673,11 +816,11 @@
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"eye_white",
|
||||
"accessory",
|
||||
"Eyebrows",
|
||||
"Eye",
|
||||
"hair",
|
||||
"rubber"
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_hoodie.glb",
|
||||
@@ -693,20 +836,21 @@
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 4826,
|
||||
"vertices": 4504
|
||||
"triangles": 6206,
|
||||
"vertices": 12988
|
||||
},
|
||||
"npc_jacket": {
|
||||
"base": "casual",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.332,
|
||||
1.824,
|
||||
0.185
|
||||
0.18,
|
||||
1.843,
|
||||
0.271
|
||||
],
|
||||
"min": [
|
||||
-0.332,
|
||||
0,
|
||||
-0.178
|
||||
-0.18,
|
||||
-0.009,
|
||||
-0.099
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
@@ -714,11 +858,11 @@
|
||||
"materials": [
|
||||
"skin",
|
||||
"cloth",
|
||||
"pants",
|
||||
"eye_white",
|
||||
"accessory",
|
||||
"Hair_Brown",
|
||||
"Brown",
|
||||
"hair",
|
||||
"rubber"
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_jacket.glb",
|
||||
@@ -734,34 +878,35 @@
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 4394,
|
||||
"vertices": 4175
|
||||
"triangles": 6424,
|
||||
"vertices": 13440
|
||||
},
|
||||
"npc_vest": {
|
||||
"base": "worker",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.321,
|
||||
1.819,
|
||||
0.185
|
||||
0.217,
|
||||
1.863,
|
||||
0.287
|
||||
],
|
||||
"min": [
|
||||
-0.321,
|
||||
0,
|
||||
-0.172
|
||||
-0.217,
|
||||
-0.004,
|
||||
-0.103
|
||||
]
|
||||
},
|
||||
"category": "npc",
|
||||
"layer": "vest",
|
||||
"materials": [
|
||||
"skin",
|
||||
"Worker_Yellow",
|
||||
"Worker_Vest",
|
||||
"cloth",
|
||||
"pants",
|
||||
"eye_white",
|
||||
"accessory",
|
||||
"hair",
|
||||
"rubber",
|
||||
"hivis",
|
||||
"reflective"
|
||||
"Moustache",
|
||||
"Eye",
|
||||
"pants"
|
||||
],
|
||||
"mesh_node": "npc_base",
|
||||
"path": "assets/models/blender/npc/npc_vest.glb",
|
||||
@@ -775,8 +920,8 @@
|
||||
"accessory",
|
||||
"hair"
|
||||
],
|
||||
"triangles": 4474,
|
||||
"vertices": 4340
|
||||
"triangles": 5240,
|
||||
"vertices": 10936
|
||||
},
|
||||
"palm_tree": {
|
||||
"bounds_godot": {
|
||||
@@ -794,7 +939,8 @@
|
||||
"category": "props",
|
||||
"materials": [
|
||||
"palm_bark",
|
||||
"palm_leaf"
|
||||
"palm_leaf",
|
||||
"palm_leaf_dark"
|
||||
],
|
||||
"path": "assets/models/blender/props/palm_tree.glb",
|
||||
"triangles": 400,
|
||||
@@ -840,7 +986,8 @@
|
||||
"materials": [
|
||||
"stone_pot",
|
||||
"soil",
|
||||
"foliage"
|
||||
"foliage",
|
||||
"foliage_dark"
|
||||
],
|
||||
"path": "assets/models/blender/props/planter_box.glb",
|
||||
"replaces": "generated/planter_box.glb",
|
||||
@@ -913,7 +1060,8 @@
|
||||
"category": "props",
|
||||
"materials": [
|
||||
"stone_pot",
|
||||
"foliage"
|
||||
"foliage",
|
||||
"foliage_dark"
|
||||
],
|
||||
"path": "assets/models/blender/props/round_planter.glb",
|
||||
"triangles": 1240,
|
||||
@@ -1097,14 +1245,14 @@
|
||||
"attach": "head",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.101,
|
||||
0.059,
|
||||
0.158
|
||||
0.065,
|
||||
0.108,
|
||||
0.101
|
||||
],
|
||||
"min": [
|
||||
-0.101,
|
||||
0.011,
|
||||
0.003
|
||||
-0.065,
|
||||
0.077,
|
||||
0.002
|
||||
]
|
||||
},
|
||||
"category": "npc_gear",
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,33 @@
|
||||
# Quaternius build sources (CC0 1.0)
|
||||
|
||||
These are the **build inputs** for the shipped NPC roster, not runtime assets.
|
||||
`blender_src/.gdignore` keeps Godot from importing this directory.
|
||||
|
||||
Source: Quaternius *Ultimate Modular Characters* and *Ultimate Modular Women*
|
||||
(<https://quaternius.com/packs/ultimatemodularcharacters.html>), licence CC0 1.0,
|
||||
attribution recorded in the repository `CREDITS.md`.
|
||||
|
||||
| File | SHA-256 | Bones | Meshes |
|
||||
| --- | --- | --- | --- |
|
||||
| `Casual.glb` | `bc2d84fba249ebaa0629cb9d133ad37234196e4b8004aa205a6397abed04e05c` | 62 | 4 skinned + Icosphere helper |
|
||||
| `Casual_Hoodie.glb` | `f5b722199f7628b24550989af4230f65d5230727df9c326fd2b3e360effe5249` | 62 | 4 skinned + Icosphere helper |
|
||||
| `Worker.glb` | `8d0f454336c2effc5c08f63a177af3a82d1a7af025ca9588b40e4c90c4703534` | 62 | 4 skinned + Icosphere helper |
|
||||
|
||||
Each file also carries 24 animation clips; the build exports **no** animation data, so
|
||||
the shipped GLBs contain the mesh, the 62-joint skin and the material slots only.
|
||||
|
||||
Reproducing the shipped characters:
|
||||
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/blender_assets.py" -- build --only npc
|
||||
|
||||
Upstream quirks the build compensates for (see `docs/npc-model-sourcing.md`):
|
||||
|
||||
1. every mesh ships a **degenerate UV map** (all loops at `0,0`) with flat-colour
|
||||
materials, so the build Smart-UV-unwraps and repacks before export;
|
||||
2. the meshes must be joined **without welding** — `remove_doubles` corrupts the
|
||||
skin weights;
|
||||
3. the rest pose is a T-pose, so the arm-down rest is baked by
|
||||
`_bake_quaternius_rest()`;
|
||||
4. the upstream material names differ per file, so each source carries a slot map
|
||||
down to the project's five tint slots.
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 922 KiB |
|
After Width: | Height: | Size: 639 KiB |
|
After Width: | Height: | Size: 501 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.eye_contact": "Look",
|
||||
"radial.point": "Point",
|
||||
@@ -88,5 +88,122 @@
|
||||
"review.timeline_button": "Body camera timeline",
|
||||
"hud.nearby": "%d nearby · Tab or mouse wheel cycles targets",
|
||||
"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/npc_profile.gd" id="2_acokf"]
|
||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_mqiqi"]
|
||||
[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_7qk8j"]
|
||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_lvgx2"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_i8fga"]
|
||||
script = ExtResource("1_2u28i")
|
||||
[sub_resource type="Resource" id="Resource_xf4wh"]
|
||||
script = ExtResource("1_c5x6v")
|
||||
required_artifacts = {
|
||||
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."
|
||||
|
||||
[sub_resource type="Resource" id="Resource_c7lhe"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_cgh52"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "tourist"
|
||||
display_name = "Tourist"
|
||||
npc_type = 0
|
||||
temperament = 0.1
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_qe635"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "confused_visitor"
|
||||
display_name = "Confused Visitor"
|
||||
npc_type = 3
|
||||
temperament = 0.2
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_u5nrp"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "delivery"
|
||||
display_name = "Delivery Worker"
|
||||
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?"])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["Drop-off for the venue. Where should I leave it?"]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_v2hc4"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_dsavb"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "street_performer"
|
||||
display_name = "Street Performer"
|
||||
npc_type = 1
|
||||
temperament = 0.45
|
||||
base_compliance = 0.55
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
0: 55,
|
||||
@@ -156,47 +63,17 @@ reactions_by_level = {
|
||||
3: 30
|
||||
}
|
||||
}
|
||||
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
|
||||
context_lines = {
|
||||
"reason": ["Just doing my act; I'll keep the sidewalk clear."]
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
|
||||
[sub_resource type="Resource" id="Resource_lg4td"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_sexiy"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "influencer"
|
||||
display_name = "Influencer"
|
||||
npc_type = 2
|
||||
temperament = 0.5
|
||||
base_compliance = 0.5
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
0: 40,
|
||||
@@ -218,47 +95,18 @@ reactions_by_level = {
|
||||
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."])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["Filming a quick clip for my channel."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_ijl2s"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_ghqd8"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "intoxicated"
|
||||
display_name = "Intoxicated Pedestrian"
|
||||
npc_type = 4
|
||||
temperament = 0.6
|
||||
base_compliance = 0.4
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
0: 30,
|
||||
@@ -272,41 +120,13 @@ reactions_by_level = {
|
||||
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
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["Looking for my friends. I think they went inside."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_eycmk"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_g8peq"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "trespasser"
|
||||
display_name = "Trespasser"
|
||||
npc_type = 5
|
||||
@@ -341,41 +161,13 @@ reactions_by_level = {
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_4xl5p"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "mental_crisis"
|
||||
display_name = "Person in Crisis"
|
||||
npc_type = 7
|
||||
@@ -411,47 +203,18 @@ reactions_by_level = {
|
||||
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
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["I just need a quiet minute.", "I'm waiting for someone."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_oa64f"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_y6282"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "violent_threat"
|
||||
display_name = "Violent Threat"
|
||||
npc_type = 6
|
||||
temperament = 0.95
|
||||
base_compliance = 0.05
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
2: 25,
|
||||
@@ -479,41 +242,12 @@ reactions_by_level = {
|
||||
7: 30
|
||||
}
|
||||
}
|
||||
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
|
||||
context_lines = {
|
||||
"reason": ["Back off. This doesn't concern you."]
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
|
||||
[sub_resource type="Resource" id="Resource_7t7dh"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_75a0r"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "protester"
|
||||
display_name = "Protester"
|
||||
npc_type = 12
|
||||
@@ -547,66 +281,34 @@ reactions_by_level = {
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_ix35t"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "authorized_staff"
|
||||
display_name = "Staff Member"
|
||||
npc_type = 8
|
||||
temperament = 0.15
|
||||
base_compliance = 1.0
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {}
|
||||
default_reactions = {
|
||||
1: {
|
||||
0: 100
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_tnpxm"]
|
||||
script = ExtResource("2_acokf")
|
||||
[sub_resource type="Resource" id="Resource_0mcg3"]
|
||||
script = ExtResource("2_7qk8j")
|
||||
id = "authorized_guest"
|
||||
display_name = "Authorized Guest"
|
||||
npc_type = 9
|
||||
temperament = 0.2
|
||||
base_compliance = 0.95
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {}
|
||||
default_reactions = {
|
||||
1: {
|
||||
0: 92,
|
||||
@@ -617,16 +319,16 @@ default_reactions = {
|
||||
2: 12
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
|
||||
}
|
||||
|
||||
[resource]
|
||||
script = ExtResource("3_mqiqi")
|
||||
script = ExtResource("3_lvgx2")
|
||||
id = "hollywood_building"
|
||||
display_name = "Hollywood Boulevard Building"
|
||||
description = "Steps and entrance of a private building off Hollywood Boulevard."
|
||||
access_policy = SubResource("Resource_i8fga")
|
||||
display_name = "El Capitan Entertainment Centre"
|
||||
description = "The historic 1921 Masonic Temple at 6840 Hollywood Boulevard, now the El Capitan Entertainment Centre."
|
||||
access_policy = SubResource("Resource_xf4wh")
|
||||
equipment = Array[String](["wristband_policy", "flashlight", "radio"])
|
||||
locations = {
|
||||
"entrance": Vector3(0, 0, 1),
|
||||
@@ -635,7 +337,7 @@ locations = {
|
||||
"sidewalk": Vector3(0, 0, 9),
|
||||
"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]([{
|
||||
"base_weight": 1.0,
|
||||
"id": "tourist_photo",
|
||||
@@ -710,6 +412,33 @@ event_pool = Array[Dictionary]([{
|
||||
"time_range_s": Vector2(300, 800)
|
||||
}, {
|
||||
"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",
|
||||
"id": "building_filming_boundary",
|
||||
"intent": "film",
|
||||
@@ -774,6 +503,15 @@ event_pool = Array[Dictionary]([{
|
||||
"probability": 0.28,
|
||||
"requires_emergency": true,
|
||||
"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 = {
|
||||
0: {
|
||||
@@ -789,6 +527,6 @@ shift_phases = {
|
||||
"start_s": 700.0
|
||||
}
|
||||
}
|
||||
shift_duration_s = 900.0
|
||||
policy_summary = ""
|
||||
door_staff_lines = {}
|
||||
door_staff_lines = {
|
||||
"wristband_check": ["Wristband, please.", "I need to see your event wristband."]
|
||||
}
|
||||
@@ -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/npc_profile.gd" id="2_wfc6m"]
|
||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_8xm7a"]
|
||||
[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_l60ad"]
|
||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_voh88"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_xha77"]
|
||||
script = ExtResource("1_lxsgk")
|
||||
[sub_resource type="Resource" id="Resource_aal3l"]
|
||||
script = ExtResource("1_3pn5m")
|
||||
required_artifacts = {
|
||||
2: true,
|
||||
3: true
|
||||
}
|
||||
grant_if_any_required = false
|
||||
minimum_age = 21
|
||||
require_id = true
|
||||
wand_screening_required = true
|
||||
policy_summary = "Government ID and a valid ticket or reservation required. Carry items are screened."
|
||||
|
||||
[sub_resource type="Resource" id="Resource_8ksyk"]
|
||||
script = ExtResource("2_wfc6m")
|
||||
[sub_resource type="Resource" id="Resource_57xgi"]
|
||||
script = ExtResource("2_l60ad")
|
||||
id = "tourist"
|
||||
display_name = "Tourist"
|
||||
npc_type = 0
|
||||
temperament = 0.1
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_wfc6m")
|
||||
[sub_resource type="Resource" id="Resource_r873e"]
|
||||
script = ExtResource("2_l60ad")
|
||||
id = "confused_visitor"
|
||||
display_name = "Confused Visitor"
|
||||
npc_type = 3
|
||||
temperament = 0.2
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_wfc6m")
|
||||
[sub_resource type="Resource" id="Resource_3xy2u"]
|
||||
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"
|
||||
display_name = "Influencer"
|
||||
npc_type = 2
|
||||
temperament = 0.5
|
||||
base_compliance = 0.5
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
0: 40,
|
||||
@@ -129,47 +89,18 @@ reactions_by_level = {
|
||||
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."])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["Filming a quick clip for my channel."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_ys0b0"]
|
||||
script = ExtResource("2_wfc6m")
|
||||
[sub_resource type="Resource" id="Resource_nsw77"]
|
||||
script = ExtResource("2_l60ad")
|
||||
id = "intoxicated"
|
||||
display_name = "Intoxicated Pedestrian"
|
||||
npc_type = 4
|
||||
temperament = 0.6
|
||||
base_compliance = 0.4
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
0: 30,
|
||||
@@ -183,41 +114,13 @@ reactions_by_level = {
|
||||
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
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["Looking for my friends. I think they went inside."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_bv0jw"]
|
||||
script = ExtResource("2_wfc6m")
|
||||
[sub_resource type="Resource" id="Resource_20knl"]
|
||||
script = ExtResource("2_l60ad")
|
||||
id = "trespasser"
|
||||
display_name = "Trespasser"
|
||||
npc_type = 5
|
||||
@@ -252,47 +155,18 @@ reactions_by_level = {
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_wfc6m")
|
||||
[sub_resource type="Resource" id="Resource_rivis"]
|
||||
script = ExtResource("2_l60ad")
|
||||
id = "violent_threat"
|
||||
display_name = "Violent Threat"
|
||||
npc_type = 6
|
||||
temperament = 0.95
|
||||
base_compliance = 0.05
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
2: 25,
|
||||
@@ -320,66 +194,33 @@ reactions_by_level = {
|
||||
7: 30
|
||||
}
|
||||
}
|
||||
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
|
||||
context_lines = {
|
||||
"reason": ["Back off. This doesn't concern you."]
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
|
||||
[sub_resource type="Resource" id="Resource_x43ed"]
|
||||
script = ExtResource("2_wfc6m")
|
||||
[sub_resource type="Resource" id="Resource_1dvkp"]
|
||||
script = ExtResource("2_l60ad")
|
||||
id = "authorized_staff"
|
||||
display_name = "Staff Member"
|
||||
npc_type = 8
|
||||
temperament = 0.15
|
||||
base_compliance = 1.0
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {}
|
||||
default_reactions = {
|
||||
1: {
|
||||
0: 100
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_07lsg"]
|
||||
script = ExtResource("2_wfc6m")
|
||||
[sub_resource type="Resource" id="Resource_7wsm0"]
|
||||
script = ExtResource("2_l60ad")
|
||||
id = "authorized_guest"
|
||||
display_name = "Authorized Guest"
|
||||
npc_type = 9
|
||||
temperament = 0.2
|
||||
base_compliance = 0.95
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {}
|
||||
default_reactions = {
|
||||
1: {
|
||||
0: 92,
|
||||
@@ -390,16 +231,16 @@ default_reactions = {
|
||||
2: 12
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
|
||||
}
|
||||
|
||||
[resource]
|
||||
script = ExtResource("3_8xm7a")
|
||||
script = ExtResource("3_voh88")
|
||||
id = "joke_shop"
|
||||
display_name = "The Joke Shop"
|
||||
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"])
|
||||
locations = {
|
||||
"door_area": Vector3(0, 0, 3),
|
||||
@@ -409,7 +250,7 @@ locations = {
|
||||
"sidewalk": Vector3(0, 0, 9),
|
||||
"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]([{
|
||||
"base_weight": 1.0,
|
||||
"id": "guest_valid",
|
||||
@@ -564,6 +405,19 @@ event_pool = Array[Dictionary]([{
|
||||
"phase": 2,
|
||||
"probability": 0.5,
|
||||
"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 = {
|
||||
0: {
|
||||
@@ -579,8 +433,6 @@ shift_phases = {
|
||||
"start_s": 700.0
|
||||
}
|
||||
}
|
||||
shift_duration_s = 900.0
|
||||
policy_summary = ""
|
||||
door_staff_lines = {
|
||||
"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."],
|
||||
|
||||
@@ -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/npc_profile.gd" id="2_fiebn"]
|
||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_2dvuq"]
|
||||
[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_g7rkt"]
|
||||
[ext_resource type="Script" path="res://src/core/venue_scenario.gd" id="3_jd45y"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_blmt0"]
|
||||
script = ExtResource("1_xxhx3")
|
||||
[sub_resource type="Resource" id="Resource_c55bb"]
|
||||
script = ExtResource("1_3vwd0")
|
||||
required_artifacts = {
|
||||
1: true,
|
||||
3: true,
|
||||
@@ -13,145 +13,53 @@ required_artifacts = {
|
||||
5: 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."
|
||||
|
||||
[sub_resource type="Resource" id="Resource_oddac"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_yyxim"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "tourist"
|
||||
display_name = "Tourist"
|
||||
npc_type = 0
|
||||
temperament = 0.1
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_lmn2c"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "confused_visitor"
|
||||
display_name = "Confused Visitor"
|
||||
npc_type = 3
|
||||
temperament = 0.2
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_if2cj"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "delivery"
|
||||
display_name = "Delivery Worker"
|
||||
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?"])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["Drop-off for the venue. Where should I leave it?"]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_ihxib"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_t47d3"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "street_performer"
|
||||
display_name = "Street Performer"
|
||||
npc_type = 1
|
||||
temperament = 0.45
|
||||
base_compliance = 0.55
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
0: 55,
|
||||
@@ -159,47 +67,17 @@ reactions_by_level = {
|
||||
3: 30
|
||||
}
|
||||
}
|
||||
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
|
||||
context_lines = {
|
||||
"reason": ["Just doing my act; I'll keep the sidewalk clear."]
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
|
||||
[sub_resource type="Resource" id="Resource_c57iy"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_j2mp5"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "influencer"
|
||||
display_name = "Influencer"
|
||||
npc_type = 2
|
||||
temperament = 0.5
|
||||
base_compliance = 0.5
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
0: 40,
|
||||
@@ -221,47 +99,18 @@ reactions_by_level = {
|
||||
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."])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["Filming a quick clip for my channel."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_bk410"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_uscqg"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "intoxicated"
|
||||
display_name = "Intoxicated Pedestrian"
|
||||
npc_type = 4
|
||||
temperament = 0.6
|
||||
base_compliance = 0.4
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
0: 30,
|
||||
@@ -275,41 +124,13 @@ reactions_by_level = {
|
||||
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
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["Looking for my friends. I think they went inside."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_jp52w"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_8aovb"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "trespasser"
|
||||
display_name = "Trespasser"
|
||||
npc_type = 5
|
||||
@@ -344,41 +165,13 @@ reactions_by_level = {
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_owl7s"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "mental_crisis"
|
||||
display_name = "Person in Crisis"
|
||||
npc_type = 7
|
||||
@@ -414,47 +207,18 @@ reactions_by_level = {
|
||||
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
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["I just need a quiet minute.", "I'm waiting for someone."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_6bscs"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_rnay4"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "violent_threat"
|
||||
display_name = "Violent Threat"
|
||||
npc_type = 6
|
||||
temperament = 0.95
|
||||
base_compliance = 0.05
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {
|
||||
1: {
|
||||
2: 25,
|
||||
@@ -482,41 +246,12 @@ reactions_by_level = {
|
||||
7: 30
|
||||
}
|
||||
}
|
||||
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
|
||||
context_lines = {
|
||||
"reason": ["Back off. This doesn't concern you."]
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
|
||||
[sub_resource type="Resource" id="Resource_n4lxh"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_lhp4m"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "protester"
|
||||
display_name = "Protester"
|
||||
npc_type = 12
|
||||
@@ -550,66 +285,34 @@ reactions_by_level = {
|
||||
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."])
|
||||
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"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_s8tjd"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "authorized_staff"
|
||||
display_name = "Staff Member"
|
||||
npc_type = 8
|
||||
temperament = 0.15
|
||||
base_compliance = 1.0
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {}
|
||||
default_reactions = {
|
||||
1: {
|
||||
0: 100
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["I'm on staff. I'm checking the entrance.", "Crew check-in; I'm working tonight."]
|
||||
}
|
||||
|
||||
[sub_resource type="Resource" id="Resource_nf2oq"]
|
||||
script = ExtResource("2_fiebn")
|
||||
[sub_resource type="Resource" id="Resource_ii8kq"]
|
||||
script = ExtResource("2_g7rkt")
|
||||
id = "authorized_guest"
|
||||
display_name = "Authorized Guest"
|
||||
npc_type = 9
|
||||
temperament = 0.2
|
||||
base_compliance = 0.95
|
||||
return_probability = 0.0
|
||||
reactions_by_level = {}
|
||||
default_reactions = {
|
||||
1: {
|
||||
0: 92,
|
||||
@@ -620,16 +323,16 @@ default_reactions = {
|
||||
2: 12
|
||||
}
|
||||
}
|
||||
reaction_delay = 0.6
|
||||
dialogue_lines = Array[String]([])
|
||||
carried_items = Array[String]([])
|
||||
context_lines = {
|
||||
"reason": ["I'm on the guest list. Here's my ID.", "A friend invited me; I have a ticket."]
|
||||
}
|
||||
|
||||
[resource]
|
||||
script = ExtResource("3_2dvuq")
|
||||
script = ExtResource("3_jd45y")
|
||||
id = "tv_studio"
|
||||
display_name = "Hollywood Blvd Late Night TV 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"])
|
||||
locations = {
|
||||
"entrance": Vector3(0, 0, 1),
|
||||
@@ -639,7 +342,7 @@ locations = {
|
||||
"stage_door": Vector3(6, 0, -2),
|
||||
"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]([{
|
||||
"base_weight": 1.0,
|
||||
"id": "audience_ticket",
|
||||
@@ -790,6 +493,15 @@ event_pool = Array[Dictionary]([{
|
||||
"phase": 2,
|
||||
"probability": 0.45,
|
||||
"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 = {
|
||||
0: {
|
||||
@@ -805,6 +517,6 @@ shift_phases = {
|
||||
"start_s": 700.0
|
||||
}
|
||||
}
|
||||
shift_duration_s = 900.0
|
||||
policy_summary = ""
|
||||
door_staff_lines = {}
|
||||
door_staff_lines = {
|
||||
"ticket_check": ["Ticket, credential, or guest-list confirmation, please.", "I need your ticket or guest-list confirmation."]
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
# 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-visual-review.md`](npc-visual-review.md#proportion-audit) (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. Blender MCP
|
||||
is optional interactive tooling (see [`asset-tooling.md#optional-interactive-blender-mcp`](asset-tooling.md#optional-interactive-blender-mcp)); this review's evidence
|
||||
came from the headless `tools/blender_assets.py` / `tools/npc_pipeline.py` pipelines plus the
|
||||
Godot capture harnesses, so it remains reproducible without MCP.
|
||||
|
||||
## 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, 152,464 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` | 5,038 / 4,756 | 1.824 / +0.000 | 7 | 106.8–122.2 | 429–440 | Contract pass; face prototype |
|
||||
| `npc_jacket` | 5,428 / 5,071 | 1.824 / +0.000 | 7 | 100.3–113.7 | 409–413 | Contract pass; face prototype |
|
||||
| `npc_coat` | 6,030 / 5,802 | 1.824 / +0.000 | 7 | 75.3–81.4 | 307–310 | Contract pass; face prototype |
|
||||
| `npc_vest` | 5,536 / 5,375 | 1.819 / +0.000 | 9 | 85.5–99.3 | 362–366 | Contract pass; face prototype |
|
||||
| `npc_dress` | 5,456 / 5,128 | 1.825 / +0.000 | 7 | 81.9–89.0 | 338–339 | Contract pass; face prototype |
|
||||
| `npc_hoodie` | 5,860 / 5,492 | 1.856 / +0.000 | 7 | 89.5–98.6 | 359–364 | Contract pass; face prototype |
|
||||
| `npc_firefighter` | 5,900 / 5,902 | 1.819 / +0.000 | 9 | 64.0–75.1 | 287–290 | Contract pass; face prototype (helmet shadow hides the lower lip) |
|
||||
| `npc_guard` | 5,602 / 5,684 | 1.819 / +0.000 | 11 | 90.0–100.8 | 374–374 | Contract pass; face prototype |
|
||||
| `npc_crowd_a` | 5,200 / 4,734 | 1.825 / +0.000 | 7 | 107–120 | 431–435 | Contract pass; crowd prototype |
|
||||
| `npc_crowd_b` | 4,734 / 4,353 | 1.809 / +0.000 | 7 | 107–118 | 434–439 | Contract pass; crowd prototype |
|
||||
| `npc_crowd_c` | 5,674 / 5,440 | 1.824 / +0.000 | 7 | 76–83 | 317 | Contract pass; crowd prototype |
|
||||
| `npc_crowd_d` | 5,814 / 5,300 | 1.856 / +0.000 | 7 | 92–106 | 377–381 | Contract pass; crowd prototype |
|
||||
| `npc_base` (fallback) | 1,204 / 2,488 | 1.836 / +0.000 | 5 | 70.1–72.9 | 4× on fabric | Contract pass; compact fallback under the 16,000/16,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 16,000/16,000 with the farthest well below the new cap 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.
|
||||
|
||||
## Baseline inventory (2026-11-04)
|
||||
|
||||
Recorded for the character baseline before the Quaternius migration. `npc_base.blend` is the
|
||||
frozen fallback; the 12 characters are currently assembled procedurally by
|
||||
`tools/blender_assets.py`. The active plan is now the CC0 Quaternius base bake
|
||||
([ADR-0017](adr/0017-quaternius-npc-base.md)); this section and the MakeHuman notes below are the
|
||||
pre-migration record. The MPFB/MakeHuman development files were removed from `tmp/` after the
|
||||
2026-11-05 pivot.
|
||||
|
||||
### `assets/models/npc/npc_base.blend` (frozen fallback)
|
||||
|
||||
- Mesh `npc_base`: **652 verts / 642 tris**, five material slots in order
|
||||
`pants, cloth, skin, hair, accessory`.
|
||||
- Armature: **14 bones** — `Hips, Spine, Neck, Head, ShoulderL/R, ElbowL/R, HandL/R, HipL/R, KneeL/R`
|
||||
(the required 13 core bones plus the `Hips` root).
|
||||
- It is the compact fallback, not the prototype host; the overhaul must not absorb prototype
|
||||
detail into it unless a separate contract decision approves it.
|
||||
|
||||
### 12 generated character GLBs
|
||||
|
||||
`audit` counts (glTF accessor; Blender `inspect` can differ ±1 on normal-split rounding):
|
||||
|
||||
| Character | Tris | Verts | | Character | Tris | Verts |
|
||||
| --- | ---: | ---: | --- | --- | ---: | ---: |
|
||||
| `npc_casual` | 5,034 | 4,607 | | `npc_firefighter` | 5,888 | 5,785 |
|
||||
| `npc_jacket` | 5,432 | 5,225 | | `npc_guard` | 5,670 | 5,671 |
|
||||
| `npc_coat` | 6,046 | 5,645 | | `npc_crowd_a` | 5,284 | 4,908 |
|
||||
| `npc_vest` | 5,532 | 5,195 | | `npc_crowd_b` | 4,812 | 4,550 |
|
||||
| `npc_dress` | 5,540 | 5,073 | | `npc_crowd_c` | 5,690 | 5,510 |
|
||||
| `npc_hoodie` | 5,864 | 5,325 | | `npc_crowd_d` | 5,810 | 5,444 |
|
||||
|
||||
All well under the 16,000/16,000 cap; the largest is `npc_coat` at 38% of the tri budget, so
|
||||
head/face/hand detail has ample headroom.
|
||||
|
||||
### Procedural builders (current geometry locations)
|
||||
|
||||
Coordinates are Blender metres, Z-up; face front is −Y / Godot +Z; `s` = ±1 side, `w` = build
|
||||
width scale.
|
||||
|
||||
- **Head** — `_face` + `_shape_head_mesh`: `sphere(Head, 0.155, (0, 0, 1.635), seg=24, rings=16,
|
||||
scale=(0.92, 1.0, 1.12))`. Forward-only sculpt (brow at z 1.695, cheek at z 1.612, jaw/chin at
|
||||
z 1.565); recessed creases rejected because they bake as dark AO patches.
|
||||
- **Eyes** — sclera `sphere(0.019, (0.052s, −0.134, 1.664), scale=(1.25, 0.62, 0.85))`;
|
||||
iris `sphere(0.0105, (0.052s, −0.141, 1.664), scale=(1.0, 0.55, 1.0))`; upper lid at z +0.014,
|
||||
lower lid at z −0.012; brow box at (0.052s, −0.143, 1.699).
|
||||
- **Nose** — bridge `cyl(0.016→0.022, (0, −0.145, 1.652))`, tip `sphere(0.022, (0, −0.162, 1.615))`,
|
||||
wings `sphere(0.012, (0.016s, −0.160, 1.606))`.
|
||||
- **Mouth** — slot `sphere(0.028, (0, −0.158, 1.583), scale=(1.0, 0.28, 0.12))`, lower lip
|
||||
`sphere(0.012, (0, −0.156, 1.578))`.
|
||||
- **Ears** — `sphere(0.036, (0.143s, 0.008, 1.63), scale=(0.42, 1.0, 1.35))` + lobe + inner concha.
|
||||
- **Hands** — `_hands_and_shoes`: palm `sphere(0.046, (0.275·s·w, −0.012, 0.762),
|
||||
scale=(0.80, 0.62, 0.88))`; four fingers base (…, 0.778) → tips z 0.701–0.714; thumb
|
||||
(…, 0.778 → 0.760 → 0.738). No finger bones; open palm + `HAND_ITEM_FORWARD` only.
|
||||
|
||||
### UV layout
|
||||
|
||||
- Five tint slots (`skin, hair, accessory, cloth, pants`) at 256² each; fixed detail slots
|
||||
(`eye_white`, rubber, hivis, reflective, leather, metal) are the extra material slots.
|
||||
- Authored texel density 64–122 px/m (below the background tier 128–256), fabric effective
|
||||
287–440 px/m via the 4× repeat; runtime tint carries the look. This is a recorded note until a
|
||||
portrait shows texture starvation.
|
||||
|
||||
### MakeHuman prototypes (historical, not shipped; research files removed from `tmp/`)
|
||||
|
||||
| File | Verts / tris | Bones | Slots | Note |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `makehuman_body.glb` | 19,320 / 6,444 | 0 | `skin_proto` | CC0 intake, unskinned, 1.82 m grounded |
|
||||
| `makehuman_retargeted.glb` | 7,062 / 6,444 | 14 | 5 tint | arm-down + contract rig, deltoid warp remains |
|
||||
| `makehuman_final.glb` | 6,651 / 6,407 | 14 | 8 (incl. `skin.001`, `eye_white`, `rubber`) | project head/hands/shoes swapped; duplicate `skin.001` |
|
||||
|
||||
The "Icosphere" the inspector lists alongside these GLBs is the glTF importer's
|
||||
bone-display helper (a `pose_bone.custom_shape`), not part of the asset; `inspect_glb` already
|
||||
excludes it from measurements. The real cleanup for `makehuman_final.glb` is the duplicate
|
||||
`skin.001` material and the manual deltoid retopology.
|
||||
|
||||
## Overhaul acceptance criteria (measurable)
|
||||
|
||||
Defined 2026-11-04 for the local part pipeline. A prototype must pass every applicable item
|
||||
before it is rolled out to the other variants; the bar is the stylized low-poly target, not
|
||||
photorealism.
|
||||
|
||||
### Face (one-variant prototype first)
|
||||
|
||||
Capture `front` (yaw 0°), `three_quarter` (yaw 35°) and `profile` (yaw 90°) at **1.5 m** and
|
||||
**1.85 m** (32° FOV), matching `capture_npc_baseline.gd` (a new 1.5 m portrait capture is added
|
||||
for this). Pass conditions:
|
||||
|
||||
- **Eyes** — sclera + iris + both lids distinct at 1.85 m; at 1.5 m the iris is a readable dark
|
||||
dot against the white sclera, not a floating oval; no socket ring or lid seam.
|
||||
- **Mouth** — the mouth line and lower-lip volume read at both distances; no moustache-like
|
||||
shadow from a floating dark slot.
|
||||
- **Nose** — bridge, tip and ala planes read in three-quarter and profile; no nose-wing seam or
|
||||
inverted normal at the base.
|
||||
- **Ears** — concha depth + lobe read in profile; not a flat disc.
|
||||
- **Jaw/cheek/chin** — front and profile show a jaw transition and cheek/chin planes; no faceted
|
||||
plane seams, inverted normals or z-fighting.
|
||||
- **Silhouette** — front/three-quarter/profile agree; face direction is unambiguous.
|
||||
- **AO** — the 256² bake stays clean: no recessed crease baking into a dark patch.
|
||||
|
||||
### Hands (one-variant prototype first)
|
||||
|
||||
Capture an **open palm** and a **relaxed grip** at **0.8 m** (`--view=hand` plus a new grip
|
||||
capture). Pass conditions:
|
||||
|
||||
- **Open palm** — four fingers and a thumb read as a hand, not a mitten; at least two of the four
|
||||
finger gaps are readable.
|
||||
- **Grip** — the palm plus curled fingers silhouette reads; no finger-through-palm clipping.
|
||||
- **Gear contact** — phone, cup, clipboard, camera, flashlight, wand and radio contact the palm
|
||||
at 0.8 m with `HAND_ITEM_FORWARD` unchanged; no floating or hand-through-item clipping.
|
||||
|
||||
### Contract (both, every prototype)
|
||||
|
||||
- 24,000 triangles / 24,000 glTF vertices per character; gear stays at 1,500.
|
||||
- Optional extra bones are allowed; the required 13 core bones and their rest positions must not
|
||||
move; the `npc_base` mesh node name and the five tint slots stay unchanged.
|
||||
|
||||
### Measurement gates
|
||||
|
||||
`blender_assets.py inspect`/`audit`/`fit`, `npc_proportions.py`, the 24-NPC budget, Godot + Python
|
||||
tests, the 48-frame baseline and the 96-frame pose/context review all stay authoritative; the new
|
||||
face/hands captures are the visual bar for this track.
|
||||
|
||||
### Working-set layout
|
||||
|
||||
Part sources and preview GLBs live under `tmp/npc-parts/<variant>/` (gitignored by the root
|
||||
`tmp/` rule); only the assembled character GLBs, AO/maps, the manifest and the review sheets are
|
||||
committed. No new committed `.blend` source tree is added without a docs/ADR decision — the
|
||||
frozen `npc_base.blend` stays the only tracked `.blend`.
|
||||
|
||||
### Head prototype (in progress 2026-11-04)
|
||||
|
||||
`tools/npc_parts.py build --part head_proto` is the first connected-shell attempt: a seg-48
|
||||
head sphere is displaced (not floating spheres) into eye sockets with an orbital rim, a nose
|
||||
bridge/tip/ala, upper/lower lip volumes, and extruded ears with a concha recess; the eyeballs
|
||||
stay separate. Local renders live under `tmp/npc-parts/npc_casual/head_proto_*.png`.
|
||||
|
||||
Iteration log (vision-judged, profile view):
|
||||
|
||||
1. Initial displacement too subtle — read as a "ball with props".
|
||||
2. Nose + ear visible in profile but the cranium still read as a "ball with a nose".
|
||||
3. Added face-plane flatten + jaw/chin taper; taper read, but "~90% sphere".
|
||||
4. Stronger flatten + nose + chin; nose now reads, jawline still dissolves into the skull.
|
||||
5. Fixed an ear-displacement sign bug (the ± side loop cancelled the helix); the ear now
|
||||
protrudes (x ±0.155 vs the ±0.139 shell) but reads as a soft blob.
|
||||
|
||||
Current state (prototype built, ~3/5 vision read): the nose, mouth notch, chin bump and ear bulge
|
||||
all read; the centre-profile measurement confirms the relief is objective — nose tip −0.192,
|
||||
brow −0.150, chin −0.149, jaw step −0.11, forehead −0.126→−0.078. The remaining polish (a flatter
|
||||
forehead, a defined ear helix/lobe, a jawline that steps to the neck) is folded into the §3.2
|
||||
variant comparison; the neck is part of `_skin_body`, so the standalone head always lacks it.
|
||||
|
||||
### AI-driven head rework (2026-11-04)
|
||||
|
||||
The head was reworked against an AI-generated target concept (front + profile, saved under
|
||||
`tmp/npc-parts/head_concept_*.png`) and judged with `describe_image.py` across several iterations.
|
||||
The displacement-on-sphere approach oscillates between "decorated egg" (too subtle) and
|
||||
"spiky wedges" (too aggressive) and settles at ~3/5 — the coarse s48 sphere cannot resolve the
|
||||
nose/lip/chin relief cleanly.
|
||||
|
||||
The judge then compared the displacement head against the **decimated MakeHuman head**
|
||||
(extracted from `makehuman_retargeted.glb`, 2,009 verts / 1,573 tris, saved as
|
||||
the local `makehuman_head.glb`) and returned a decisive result: **ship the MakeHuman head** —
|
||||
it keeps the nose/chin/jaw landmarks at a fraction of the cost, while the procedural head's
|
||||
"clean ellipse outline instantly screams procedural sphere".
|
||||
|
||||
**Conclusion:** the next head asset is the MakeHuman head shape (1,573 tris) plus the project's
|
||||
separate eye/mouth/brow pieces for readability, not the displacement shell. The displacement
|
||||
`_head_proto` stays local as a rejected variant.
|
||||
|
||||
**Built (2026-11-04):** `_makehuman_head` in `blender_assets.py` imports the committed
|
||||
`assets/models/blender_src/makehuman_head.glb` (CC0, attributed in `CREDITS.md`), collapses it to
|
||||
one skin slot, bisects + caps the ragged neck, and adds the project sclera/iris/lids/brows/mouth
|
||||
at the MakeHuman landmarks. `build_character(name, head="mh")` assembles it; the result passes
|
||||
`inspect` (14 bones, skinned, no issues). The judge reads the face ~3.5/5; remaining polish is
|
||||
welding the added eye/brow pieces into the head shell and a skull-conforming hair cap.
|
||||
|
||||
**Standalone head re-review (2026-11-04):** the separate
|
||||
`assets/models/blender_src/makehuman_head.glb` prototype (12,641 tris / 7,690 split verts,
|
||||
with a skin eyelid mask, recessed globes, flat iris/pupil discs and surface-anchored brows)
|
||||
was **accepted by the owner** as a basic human head. The automated vision gate still returns
|
||||
REJECT on the eye/lid region (globe depth, lid overlap, iris at three-quarter) as deferred
|
||||
polish. The assembled `npc_casual --head mh` now adds a skull-conforming hair cap, a hidden
|
||||
scalp trim and a narrowed neck/shoulder body: 14,753 tris / 12,823 seam-split verts /
|
||||
14 bones / 9 materials, `inspect` clean. All 12 variants count 14,693–15,987 tris. See
|
||||
[`makehuman-head-review.md`](makehuman-head-review.md).
|
||||
|
||||
### Head subdivision comparison (2026-11-04)
|
||||
|
||||
Three `head_proto` variants at UV-sphere segments 24/48/64 were rendered at 1.85 m and 1.5 m in
|
||||
front/three-quarter/profile and judged side by side:
|
||||
|
||||
| Variant | Verts / tris | 1.85 m | 1.5 m | Verdict |
|
||||
| --- | ---: | --- | --- | --- |
|
||||
| s24 | 670 / 1,168 | lumpy, nose/lips melt into the chin | jagged silhouette, no mouth | reject |
|
||||
| s48 | 1,558 / 2,944 | eyes/brows/nose/lips read cleanly | features resolve cleanly | **pick** |
|
||||
| s64 | 2,470 / 4,768 | visually identical to s48 | indistinguishable from s48 | reject (wasted verts) |
|
||||
|
||||
The chosen s48 is the `head_proto` default; the rejected s24/s64 stay local under
|
||||
`tmp/npc-parts/`. A before/after sign-off sheet (current `_face` vs s48, 1.85 m
|
||||
front/three-quarter/profile) is
|
||||
[`blender_previews/sheet_head_proto_before_after.jpg`](blender_previews/sheet_head_proto_before_after.jpg).
|
||||
|
||||
## 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 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| *(pose captured 2026-11-04: see "Pose evidence" below)* | | | | | | | |
|
||||
| `npc_casual` | accept (0.9.4) | accept (face prototype) | accept (open palm) | fixed (wrist/hand + hip/knee) | accept | accept (two-quad impostor) | pending owner |
|
||||
| `npc_jacket` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
|
||||
| `npc_coat` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept (hem 0.76 stride) | accept | pending owner |
|
||||
| `npc_vest` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
|
||||
| `npc_dress` | accept; performer props dominate | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
|
||||
| `npc_hoodie` | accept (hooded criterion) | accept (lower face only) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
|
||||
| `npc_firefighter` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
|
||||
| `npc_guard` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | pending owner |
|
||||
| `npc_crowd_a` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | crowd prototype |
|
||||
| `npc_crowd_b` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept (beard) | accept | crowd prototype |
|
||||
| `npc_crowd_c` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept (coat hem) | accept | crowd prototype |
|
||||
| `npc_crowd_d` | accept | accept (face prototype) | accept | fixed (wrist/hand + hip/knee) | accept | accept | crowd prototype |
|
||||
|
||||
### Gear (18)
|
||||
|
||||
| Gear | Attach | Tris | Verdict |
|
||||
| --- | --- | --- | --- |
|
||||
| `cap` | head | 328 | — (fit measured, visual pending) |
|
||||
| `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).
|
||||
|
||||
`blender_assets.py fit` readings (2026-11-03): `shoulder_bag` is clear on both `npc_coat` and
|
||||
`npc_dress` (the only back-gear clearance the fit test asserts); `sunglasses` sits inside the
|
||||
head band at 0.64× head width. Three caveats worth recording, none of which changes a verdict:
|
||||
`backpack` (npc_casual) and `scba_tank` (npc_firefighter) report `CLIP` because back gear is
|
||||
measured against the **hip/skirt** radius, which is wider than the upper back where the bags
|
||||
actually sit; `cap` and `firefighter_helmet` report `band=OUTSIDE` because they sit on the crown
|
||||
above the 1.55–1.81 face/head band (expected); and the `lanyard` clip is corrected at runtime by
|
||||
`GEAR_OFFSETS` (+4 cm forward). The unused `beanie` was retired on 2026-11-03 (removed from
|
||||
`GEAR`, its builder, `npc_avatar.gd` and the manifest).
|
||||
|
||||
### 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 | accept (2026-11-03 flared base collar) |
|
||||
| `bench` | 560 | — |
|
||||
| `trash_bin` | 228 | — |
|
||||
| `planter_box` | 1,324 | — |
|
||||
| `newspaper_box` | 300 | accept (2026-11-03 recessed slot + door seam) |
|
||||
| `fire_hydrant` | 610 | accept (2026-11-03 rebuild: bolted flange, bonnet cap nut, larger side + front outlets) |
|
||||
| `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 | — |
|
||||
|
||||
Vision review 2026-11-03 (`tools/describe_image.py` on the prop gallery): `bench` and `trash_bin`
|
||||
are ship-ready (minor chamfer polish only); `bollard` passes but leans on its reflective band
|
||||
(add a base collar); `planter_box`/`round_planter` fail on the foliage — the `_shrub()` clumps
|
||||
read as rocks, not plants (same foliage language gap, gated on the owner); `newspaper_box` is
|
||||
borderline (the slot is a flat decal, wants a recessed inset + door seam); `fire_hydrant` is now
|
||||
rebuilt (see row). No prop floats or sinks; shadow direction is consistent.
|
||||
|
||||
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 | 26,828 tris / 580 meshes | facade review landed: 6.0 m low hall, white signature band, tied-back curtains, west hotel tower; remaining gap = rounded west corner |
|
||||
| TV Studio | — | shape-language review pending |
|
||||
|
||||
## Pose evidence (fixed 2026-11-04)
|
||||
|
||||
The weights/pose column can now be filled. The root cause was the harness: it never disabled the
|
||||
avatar's `_physics_process`, so `_advance_fsm` + `_move_toward_target` reset `arrived`/velocity
|
||||
every tick and `theater_pose()` reverted to idle. `capture_npc_pose.gd` now calls
|
||||
`set_physics_process(false)`/`set_process(false)` after staging, and the pose sticks (sit vs idle
|
||||
diff 5.63, walk vs idle 2.29, vision-judged as crouched). The earlier "moving the `Rig` child does
|
||||
not render" probe was a red herring: it set the position in `_shoot()` in the same frame as
|
||||
`root.get_texture().get_image()`.
|
||||
|
||||
`src/test/capture_npc_pose.gd` stages idle / walk-contact / walk-pass / seated for all twelve
|
||||
variants (full-body + shoulder views).
|
||||
|
||||
| Check | Result |
|
||||
| --- | --- |
|
||||
| Pose reaches proxies | Seated pose logs `rig_y=-0.339`, `leg0=-1.357` |
|
||||
| Pose reaches skeleton bones | `HipL`/`KneeL` bone rotations differ from rest; headless test `test_pose_reaches_the_skeleton_bones` locks it (suite 273/273) |
|
||||
| Idle restores rest | Same test asserts the hip returns to its rest orientation |
|
||||
| Skinning bound | One `Skeleton3D`, mesh bound to it (`same=true`); hiding `npc_base` removes the body |
|
||||
| Bone rotations deform the mesh | Direct writes change rendered pixels: Spine 1,719 / HipL 1,052 / KneeL 810 / ElbowL 745 / Head 671 / ShoulderL 640 sampled pixels |
|
||||
| Bone translation deforms the mesh | A +0.5 m Head offset moves the head in frame |
|
||||
| Standalone window probes render the pose | Three variants (avatar under `root`, under a `Node3D` stage, created mid-`_process`) render the seated crouch (diff ~2,200-2,360 samples) |
|
||||
| Offscreen capture renders the pose | A `SubViewport` + `RenderingServer.force_draw()` probe renders the crouch reliably (diff 602 at +2/+6/+12 frames), including a re-created second avatar |
|
||||
| The harness renders the pose | **No** in its current form: same stage, same pose, rest frames. One ordering (`_frame_view()` immediately before `force_draw()`) produced posed walk frames once, then regressed to rest/garbage on the next run |
|
||||
|
||||
The rig, the animation math and the offscreen render path are all sound. A 2026-11-03 re-attempt
|
||||
with the recipe (SubViewport + `force_draw()` + `force_update_all_bone_transforms()` + disabled
|
||||
process/physics) still produced byte-identical rest frames, and a decisive probe narrowed the
|
||||
failure further: teleporting the **avatar** changes the render (diff 10), but moving the **`Rig`
|
||||
child** (the GLB model's parent) does not (diff 0). The skinned mesh therefore does not follow its
|
||||
`Rig` Node3D transform, so the rig lowering never reaches the rendered model — the defect is
|
||||
upstream of camera/`force_draw()` ordering. Next attempt must answer why the skinned model ignores
|
||||
the `Rig` node, or stage poses via the known-good `capture_npc_review.gd` fallback.
|
||||
|
||||
Working recipe (verified by the probes; keep it when rebuilding):
|
||||
|
||||
1. Create a `SubViewport` (1280x720, `own_world_3d = true`, `UPDATE_ALWAYS`) in `_initialize`.
|
||||
2. Build the studio (environment, key/fill light, ground, camera) and one avatar inside it.
|
||||
3. Wait 2-3 frames so the skinned mesh binds.
|
||||
4. Apply the pose with `_animate()` ticks, with the avatar's process/physics disabled.
|
||||
5. Call `RenderingServer.force_draw()`, then `viewport.get_texture().get_image()`.
|
||||
6. Free the avatar and repeat for the next pose; a re-created avatar also rendered correctly in a probe.
|
||||
|
||||
## 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.
|
||||
|
||||
The eight library characters already carry the 0.9.4 face detail in
|
||||
`tools/blender_assets.py._face()`: sculpted chin plate + cheekbones (`_shape_head_mesh`),
|
||||
socketed sclera + thin upper lid, nose bridge/tip/wings, a rounded mouth and ear concha
|
||||
(the plain sphere head in `tools/npc_pipeline.py` is the frozen `npc_base` fallback only).
|
||||
So this gap is a readability/scale question on the existing sculpt — the head is
|
||||
`seg=18/rings=12` with small offsets — not missing geometry. Concrete next step, if funded:
|
||||
a single-variant `npc_casual` prototype that raises head subdivision and deepens the
|
||||
cheek/brow offsets, then re-bakes AO and captures the portrait before touching the other seven.
|
||||
|
||||
**Prototype landed 2026-11-03/04:** `npc_casual` now has `seg=24/rings=16` (smoother), a subtle
|
||||
brow ridge, a fuller lower lip and both eyelids (upper + lower); recessed creases (nasolabial,
|
||||
temple, chin cleft) were rejected because they bake as dark AO patches. Vision-reviewed: the new
|
||||
face reads cleaner and more realistic than the old (set-in eyes, brow break, lip volume, no
|
||||
faceting); tris 4,482 → 5,038 (still under 16,000). The other seven variants keep the old face
|
||||
until this prototype is approved.
|
||||
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 (the unused `beanie` was retired 2026-11-03).
|
||||
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.
|
||||
|
||||
## qa-review sign-off (2026-11-04)
|
||||
|
||||
All eight library characters **ship with notes**. Face realism landed on all eight (`seg=24/rings=16`,
|
||||
brow ridge, both eyelids, fuller lower lip), hands/grip and cloth/hair are accepted as recorded, gear
|
||||
fit is measured with its caveats, and the props are reviewed (hydrant/bollard/newspaper_box rebuilt,
|
||||
foliage two-toned). The only unresolved column is **weights/pose** (blocked on the capture harness),
|
||||
and the full carded-cluster foliage pass is the one remaining owner-gated P1. No P0 remains.
|
||||
|
||||
## 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
|
||||
|
||||
- Review the assembled [`makehuman-head-review.md`](makehuman-head-review.md) prototype (hair cap and full-body silhouette) before rebuilding the 12 shipped character GLBs.
|
||||
|
||||
|
||||
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,53 @@
|
||||
# Gig Line Documentation
|
||||
|
||||
This folder holds the project documentation, visual references and generated QA evidence.
|
||||
|
||||
## Current priorities
|
||||
|
||||
1. [Quaternius NPC base migration](../TODO.md#quaternius-npc-base-migration-top-priority) — CC0 Quaternius mesh/animation source baked to the 24,000-budget runtime contract.
|
||||
2. [3D model and local map improvements](../TODO.md#3d-model-and-local-map-re-evaluation) — asset re-evaluation and local map reconstruction.
|
||||
3. [0.10.0 release candidate](../TODO.md#milestone-0100--3d-model-and-map-improvements-minor) — 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
|
||||
- [`makehuman-head-review.md`](makehuman-head-review.md) — retained MakeHuman fallback head, provenance and review verdict
|
||||
- [`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
|
||||
- [`../tools/README.md`](../tools/README.md) — tool inventory, categories and test runner
|
||||
- [`gameplay.md`](gameplay.md) — controls, venues and escalation
|
||||
- [`venues.md`](venues.md) — Hollywood, Joke Shop and TV Studio real-site mapping
|
||||
- [`npc-visual-review.md`](npc-visual-review.md) — NPC visual baseline, acceptance criteria and ranked gaps
|
||||
- [`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
|
||||
- [`release-qa.md`](release-qa.md) — manual smoke checklist and balance-acceptance 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
|
||||
- [`ui-copy.md`](ui-copy.md#screen-design-judge-report) — screen/button inventory and latest judge result
|
||||
|
||||
## Quick preview
|
||||
|
||||
| Reference | Capture |
|
||||
| --- | --- |
|
||||
| Venue design | <img src="screens/designs/venue_overview.png" width="320" alt="Venue overview design"> |
|
||||
| Current entrance (day) | <img src="screenshots/01-venue-overview.png" width="320" alt="In-game venue overview"> |
|
||||
| Current entrance (late) | <img src="screenshots/05-late-night-avenue.png" width="320" alt="Late-night boulevard"> |
|
||||
| Joke Shop | <img src="screenshots/12-joke-shop.png" width="320" alt="Joke Shop at night"> |
|
||||
| Character sheet | <img src="blender_previews/sheet_npc.jpg" width="320" alt="NPC character sheet"> |
|
||||
| Neutral NPC baseline | <img src="blender_previews/sheet_npc_baseline.jpg" width="320" alt="Neutral NPC baseline sheet"> |
|
||||
| Texture refinement | <img src="blender_previews/sheet_texture_refinement_0.9.5.jpg" width="320" alt="Texture refinement comparison"> |
|
||||
| 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
|
||||
- Install them manually from the
|
||||
[Godot downloads](https://godotengine.org/download/) page by placing the
|
||||
`Godot_v4.3-stable_export_templates.tpz` contents into
|
||||
`~/.local/share/godot/export_templates/4.3.stable/`.
|
||||
`Godot_v4.7.2-stable_export_templates.tpz` contents into
|
||||
`~/.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 local development machine uses 4.7.2 with its matching templates. Verify with:
|
||||
The templates must match the editor binary you use locally. CI and local
|
||||
development both use Godot 4.7.2 with its matching templates. Verify with:
|
||||
|
||||
```bash
|
||||
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
|
||||
(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)
|
||||
|
||||
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.
|
||||
- **Tags (`v*`)** and **manual `workflow_dispatch`** runs additionally execute the
|
||||
**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
|
||||
> `./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
|
||||
> `actions/cache` times out against GitHub's cache API. We dropped it (Godot import
|
||||
> is only ~4s). If a host admin later enables Gitea `[cache.actions]` with a cache
|
||||
> server, the step can be reintroduced.
|
||||
> **Godot cache evaluation:** Gitea Actions cache was tested for the Godot binary and
|
||||
> trimmed 4.7.2 templates. Restores worked intermittently, but cache saves timed out
|
||||
> in `CreateCacheEntry` / were rejected as reserved by another job. The cache steps
|
||||
> 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
|
||||
> `.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.
|
||||
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>`;
|
||||
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
|
||||
> 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:
|
||||
|
||||
### Via the web UI
|
||||
|
||||
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).
|
||||
|
||||
### Via the API
|
||||
@@ -171,7 +205,7 @@ change, update `tools/build_releases.sh` first.
|
||||
|
||||
- [ ] All headless tests pass (exit code 0).
|
||||
- [ ] 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.
|
||||
- [ ] Export templates used are the official ones for the pinned engine.
|
||||
- [ ] Artifacts uploaded; README downloads page shows them.
|
||||