Compare commits

...
6 Commits
Author SHA1 Message Date
zwitschi 0044f5f065 Record the v0.9.5 release build
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 21:12:00 +01:00
zwitschi 4bc6390e9f Release 0.9.5 texture and material refinement
Full build / Import + headless tests (push) Successful in 46s
Full build / Full desktop build + optional release (push) Successful in 4m54s
2026-11-02 21:04:48 +01:00
zwitschi fb3da52438 Refine environment surfaces and skyline haze
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 20:38:58 +01:00
zwitschi d7220c1a0a Refine NPC fabric UVs and remove baked AO blotches
Full build / Import + headless tests (push) Successful in 37s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 20:28:25 +01:00
zwitschi 31574b8d82 Raise NPC texture maps to 256
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 20:16:30 +01:00
zwitschi b60f67548d Record the v0.9.4 release build
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 43s
2026-11-02 19:58:40 +01:00
71 changed files with 371 additions and 124 deletions

No files matched your search

+13
View File
@@ -4,6 +4,19 @@ All notable user-facing changes to Gig Line. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
Semantic Versioning.
## [0.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
+79
View File
@@ -1878,3 +1878,82 @@ and `npc_proportions.py` pass, the full suite is 262/262 and the 24-NPC smoke is
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.
+48 -17
View File
@@ -63,10 +63,32 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
`character_override` because it has no spawned NPC role. **Signed off
2026-11-02:** 96 final-context captures, no P0; lanyard/helmet P1 fixes; the
backpack straps, caps, low-poly hands/faces and optional close-range
flashlight wash were accepted or deferred. The 24-NPC budget is 6.93 ms, the
flashlight wash were accepted or deferred. The 24-NPC budget is 6.94 ms, the
gallery is 13 good / 0 bad, the screen-design judge is 13/13 and a clean
portrait measured a 0.732 vision gap against the photoreal concept. No
release build, version bump or tag was created at this gate.
- **NPC texture resolution (0.9.5, 2026-11-02):** `tools/npc_pipeline.py` now generates 256²
albedo/normal/ORM maps instead of 128² (AI shirt/trousers are resized to 256). The base and all
eight library GLBs plus AO maps were rebuilt; texture files total ~0.5 MB, eight GLBs ~5.75 MB,
and the 24-NPC smoke is 6.94 ms.
- **0.9.5 texture/UI audit (2026-11-02):** all 21 screen-art textures are
1280×720 and referenced by their screens; access cards are 256×128 and HUD
icons 64×64, with no oversized or unused runtime texture. The 24-NPC smoke now
reports **168 meshes / 131,616 triangles / 6.94 ms** (50 ms budget). Compressed
payloads are picks **24.55 MB**, UI art **22.67 MB**, NPC maps **0.48 MB**.
Legacy root textures remain only for `generate_textures.py` and the asset test.
- **Environment surface audit (0.9.5, 2026-11-02):** `capture_realism.gd` gained
`surfaces` (Hollywood pavers/asphalt/limestone/facade) and `surfaces_joke`
(neighbour awning, canopy, pavers) stages with the HUD hidden. The Walk of Fame
band now uses `T_CONCRETE` as dark terrazzo; the neighbour awning uses a neutral
tint (`0.62`) and 4× UV so the red/cream stripes survive; the skyline shader
fades its lower 42 m toward a per-phase haze colour. Brick/asphalt picks needed
no replacement. No seams or broken UV scales found.
- **NPC fabric UV/AO (0.9.5, 2026-11-02):** cloth/pants use
`Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`, and `NpcAvatar` skips the baked per-character
AO on those two slots. The 256² AO was magnifying into large blotches; the fabric now reads as a
fine weave. Skin/hair/gear keep baked AO. Test `test_fabric_uv_scale_avoids_the_baked_ao_pattern`
guards this. Current suite is 263/263.
- **NPC visual baseline (0.9.4, 2026-11-02):** `src/test/capture_npc_baseline.gd` is the
neutral-studio 8-variant × 6-view baseline (front, three-quarter, profile, back, full body,
crowd). It uses a fixed orbit camera, mid-grey studio backdrop, key/fill lights, no HUD/crowd/animation;
@@ -152,16 +174,25 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
for every new `Button`; CheckButton/OptionButton keep the engine theme. Timeline
Previous/Next disable at the ends; F1 tutorial skip is keyboard-only by design.
- **Version and roadmap renumber (2026-11-02):** source was **0.9.3** at the UI
polish milestone, now **0.9.4** after the NPC pass; the
completed map/NPC work and UI polish are the patch content. Next work is
**0.9.4 NPC**, **0.9.5 textures**, **0.9.6 integration**, then the **0.10.0**
polish milestone, **0.9.4** after the NPC pass, now **0.9.5** after the texture
and material pass. Next work is **0.9.6 integration**, then the **0.10.0**
minor release candidate. `TODO.md` plans only 0.x patch/minor increments; no
major-version scope is defined yet. The v0.9.4 tag is the release trigger; no
major-version scope is defined yet. The `v0.9.5` tag is the release trigger; no
tag was created for 0.9.3.
- **Priority change (2026-11-02):** the map optimization, pre-build NPC revisit,
0.9.3 UI polish and 0.9.4 NPC visual optimization are complete and in `DONE.md`.
0.9.5 textures and 0.9.6 integration/0.10.0 release work are next. The current
source version is 0.9.4; the v0.9.4 tag triggers the CI build.
0.9.3 UI polish, 0.9.4 NPC visual optimization and 0.9.5 texture/material pass
are complete and in `DONE.md`. 0.9.6 integration and 0.10.0 release work are
next. The current source version is 0.9.5.
- **Release v0.9.5 (2026-11-02):** commit `4bc6390` tagged `v0.9.5`; Gitea Actions run
922 passed checks and the full build and published seven assets (Linux, Windows,
macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page:
`https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.5`. Do not
reuse the tag; future fixes need a new patch tag.
- **Release v0.9.4 (2026-11-02):** commit `a57b86c` tagged `v0.9.4`; Gitea Actions
run 916 passed checks and the full build and published seven assets (Linux,
Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page:
`https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4`. Do not
reuse the tag; future fixes need a new patch tag.
- **Hollywood landmark map (2026-11-02):** the default venue is the real El Capitan
Entertainment Centre block at 6840 Hollywood Blvd, not a generic brick entrance. Its
reference/mapping notes live in `docs/hollywood-boulevard-map.md`; the map-budget test
@@ -184,7 +215,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
`JokeGround_*`; `set_night_mode()` owns their energies and the hill tint.
The latest local realism judge reports a 0.454 reference gap on the night entrance;
the concept is photoreal while the map keeps stylized materials, so texture/foliage
detail stays with the postponed 0.9.5 pass rather than the map milestone.
detail stays with the 0.9.5 texture pass rather than the map milestone.
- **Docs validation (2026-11-02):** `python3 tools/check_docs.py` requires a README
only in directories containing tracked files, verifies local links and requires
every tracked image in a folder to be linked from its README. Gitignored
@@ -253,7 +284,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
gpt-5-image-mini rejects (HTTP 400); pass `--request-size 1024` for 1:1 roles.
`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`
(layers AI shirt→cloth, trousers→pants albedos at 256px) 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 /
@@ -798,7 +829,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- Venue `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI.
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
- Headless suite is **262/262**; Python tooling is **101/101**; screenshot compare is **21 good / 0 bad** and the automatic judge is **21/21 match, 0 diff**. Remaining visual follow-ups are in `TODO.md`.
- Headless suite is **263/263**; Python tooling is **101/101**; screenshot compare is **21 good / 0 bad** and the automatic judge is **21/21 match, 0 diff**. Remaining visual follow-ups are in `TODO.md`.
- Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args).
- 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
@@ -822,15 +853,15 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
2026-11-02 the playable build covers the three venues, tutorials/campaign/checkpoints, the 0.9.3
UI/accessibility pass, the 0.9.4 NPC visual optimization, and the Blender + realism asset pipeline. `TODO.md` holds the remaining
0.9.4–0.10.0 work. The project uses a custom headless runner, not GUT, and Gitea Actions, not
UI/accessibility pass, the 0.9.4 NPC visual optimization, the 0.9.5 texture/material pass, and the Blender + realism asset pipeline. `TODO.md` holds the remaining
0.9.6–0.10.0 work. The project uses a custom headless runner, not GUT, and Gitea Actions, not
GitHub Actions.
<!-- 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.
- **None (2026-11-02).** The 0.9.5 texture pass is committed, tagged `v0.9.5` and
published with seven assets; the workspace is clean apart from local/gitignored
realism and capture output. Keep the rule from `AGENTS.md`: check `git status`
before committing, and never revert another contributor's in-flight files.
+1 -1
View File
@@ -176,7 +176,7 @@ 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.9.4`). 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.5`). 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.
## Design Direction
+12 -10
View File
@@ -38,12 +38,13 @@ precision walk, the Joke Shop's Sunset Strip frontage, the TV Studio arrival, an
> Downloads are public (no sign-in required) since the host's instance-wide
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
>
> **Latest published: v0.9.4** — the NPC visual optimization pass: face/head
> depth, hands and gear placement, shoes and boots, headgear clearance, and the
> 8,000/8,000 character mesh budget on top of the Hollywood/Joke Shop maps and
> **Latest published: v0.9.5** — the texture and material refinement pass:
> 256² NPC maps, a four-times fabric weave without baked AO blotches, repair of
> the Walk of Fame terrazzo band and awning stripe, and per-phase skyline haze,
> on top of the v0.9.4 NPC visual optimization, the Hollywood/Joke Shop maps and
> the v0.9.3 UI/accessibility polish.
>
> **Current source: v0.9.4** — tagged and built from the same commit.
> **Current source: v0.9.5** — tagged and built from the same commit.
> 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
@@ -121,18 +122,19 @@ Full setup, every run/test/smoke command, and the controls table are in
- [`docs/development-status.md`](docs/development-status.md) — Implemented features and what's next.
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md) — Manual smoke-test checklist.
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
- [`docs/npc-visual-review.md`](docs/npc-visual-review.md) — 0.9.4 NPC baseline, acceptance criteria and ranked visual gaps.
- [`docs/npc-visual-review.md`](docs/npc-visual-review.md) — 0.9.4 NPC baseline, acceptance criteria and ranked visual gaps; 0.9.5 texture comparison is in [`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`](docs/blender_previews/sheet_texture_refinement_0.9.5.jpg).
- [`docs/npc-proportion-audit.md`](docs/npc-proportion-audit.md) — NPC proportion measurements against the shared stylized guide.
- [`docs/adr/`](docs/adr/) — Architecture Decision Records.
- [`TODO.md`](TODO.md) — Active tasks plus the 0.x gap analysis and milestone roadmap.
## Development Status
v0.9.4 is the current source version: the completed NPC visual optimization
(face/head depth, hands and gear placement, shoes/boots, headgear clearance and
the 8,000/8,000 mesh budget), on top of the v0.9.3 UI/accessibility polish
pass and v0.9.2's Hollywood/Joke Shop map rebuilds. It builds on
v0.9.1's Godot 4.7.2 baseline, v0.9.0's production-quality pass, v0.8's
v0.9.5 is the current source version: the completed texture and material
refinement (256² NPC maps, a 4× fabric weave with baked-AO removal, repaired
Walk of Fame/awning surfaces and per-phase skyline haze), on top of the v0.9.4
NPC visual optimization and the v0.9.3 UI/accessibility polish pass. It builds on
v0.9.2's Hollywood/Joke Shop map rebuilds, v0.9.1's Godot 4.7.2 baseline,
v0.9.0's production-quality pass, v0.8's
tutorial/campaign/resume work and the v0.7.x venue/gear/judge work. See [`docs/development-status.md`](docs/development-status.md)
for the implemented list and [`TODO.md`](TODO.md) for the 0.x roadmap. Manual
verification uses the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
+7 -45
View File
@@ -5,9 +5,9 @@ see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these
## Gap Analysis and 0.x Roadmap
Baseline: **v0.9.4** (2026-11-02). Three playable venues, tutorial/campaign/checkpoint resume,
Baseline: **v0.9.5** (2026-11-02). Three playable venues, tutorial/campaign/checkpoint resume,
difficulty/help, audio/accessibility settings, a 24-NPC budget baseline, a complete
escalation/access/radio loop, shift review, the Blender asset library, 262 headless + 101 Python
escalation/access/radio loop, shift review, the Blender asset library, 263 headless + 101 Python
tests, a 21/21 clean screen-design judge and an optional Forward+ Desktop HQ build. This is a strong
release-candidate foundation, not yet a 0.10.0 release candidate.
@@ -37,7 +37,7 @@ release-candidate foundation, not yet a 0.10.0 release candidate.
| Animation/presentation | Procedural pose layers, portrait captures, reduced hem clipping, 8,000/8,000 NPC cap | Thorough NPC silhouette/face/gear/pose/LOD optimization before visual QA | Close-up jank reduces perceived quality | Complete (0.9.4) |
| Audio/accessibility | Procedural ambience/SFX, volume controls, captions/reduce-motion settings, searchable Help | Full accessibility/localization acceptance review, audio caption coverage and P0/P1 fixes | Public release cannot claim accessible play | 0.10.0 |
| Localization | English JSON table, validation and input-generated Help | Pseudo-locale/layout audit and translator workflow sign-off | Text expansion/clipping at release | 0.10.0 |
| Performance/platform | 24-NPC headless budget (6.9 ms), Compatibility/Web/HQ paths | Target hardware matrix, Web/HQ manual profiling, frame-time/memory evidence | Platform-specific drops or startup failures | 0.9.6 / 0.10.0 |
| Performance/platform | 24-NPC headless budget (6.94 ms), Compatibility/Web/HQ paths | Target hardware matrix, Web/HQ manual profiling, frame-time/memory evidence | Platform-specific drops or startup failures | 0.9.6 / 0.10.0 |
| Save lifecycle | Settings/campaign/history/checkpoint files, atomic writes | Migration fixtures from 0.8/0.9, fresh-install/upgrade verification, rollback instructions | Player progress loss on upgrade | 0.10.0 |
| QA/CI | Headless suite, smokes, screenshot compare, 21/21 judge | Release-candidate smoke matrix, final gallery and release dry run | Regressions escape the release gate | 0.9.6 / 0.10.0 |
| Release/legal | Unsigned desktop/macOS builds, Web/HQ packages | Signing/notarization decision, checksums, AI-asset provenance, credits, privacy/body-camera and rating review | Public release blocked by trust/policy | 0.10.0 |
@@ -45,48 +45,10 @@ release-candidate foundation, not yet a 0.10.0 release candidate.
| Future 0.x | Stable 0.x feature set | Live replay, more venues, VO/music, image-to-mesh props and seasonal content | Scope creep before release | 0.11.0+ |
**Current priority:** map optimization, the **pre-build NPC revisit**, the
**0.9.3 UI polish** and **0.9.4 NPC visual optimization** passes are complete
(all moved to `DONE.md` on 2026-11-02). 0.9.5 texture refinement, 0.9.6
integration and the 0.10.0 release candidate are queued. The source version is
now 0.9.4.
## Postponed — 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
- [ ] 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.
- [ ] 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.
- [ ] Check UV scales and material roughness/metallic profiles; keep day/night
phase behavior and the wet-street transition intact.
- [ ] Review distant skyline haze and foliage/planter surfaces against the concept
references; fix obvious flatness without regressing readability.
### Character and prop textures
- [ ] Audit NPC skin, cloth, pants, hair and gear albedo/normal/AO maps in portraits;
refine fabric weave, skin variation and fixed detail materials.
- [ ] Re-bake AO where geometry changes; verify the 256² maps and material wiring.
- [ ] Audit library props and UI/card/icon textures at their actual screen sizes;
remove blurry/oversized or unused textures where safe.
- [ ] Run texture audits, import, and the full asset test suite; record texture memory
and triangle/draw-call impact.
### Visual verification
- [ ] Run `python3 tools/scene_concepts.py realism` for the affected stages with the
judge enabled; record metric gaps and model/spend.
- [ ] Re-run `tools/check_screenshots.sh` and the screen-design judge; clear all
`diff` verdicts.
- [ ] Capture before/after close-ups for brick, pavement, asphalt, character cloth
and skyline; document accepted trade-offs.
- [ ] Update asset docs and the changelog/status with the promoted texture versions.
**0.9.3 UI polish**, **0.9.4 NPC visual optimization** and **0.9.5 texture
refinement** passes are complete (all moved to `DONE.md` on 2026-11-02).
0.9.6 integration and the 0.10.0 release candidate are next. The source version
is now 0.9.5; the v0.9.5 release is published.
## Postponed — Patch 0.9.6 — Integration, performance and judge gate
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1003 B

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+1 -1
View File
@@ -37,7 +37,7 @@ This document follows the structure of the arc42 architecture documentation temp
- **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible.
- **PATCH** — bug fixes and small polish that do not change behavior.
The single source of truth for the version is `config/version` in `project.godot` (currently `0.9.4`). Each release is tagged in git as `v<version>` (e.g. `v0.6.0`). The `0.x` series is pre-stable, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md).
The single source of truth for the version is `config/version` in `project.godot` (currently `0.9.5`). Each release is tagged in git as `v<version>` (e.g. `v0.6.0`). The `0.x` series is pre-stable, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md).
## 3. Context and Scope
+37 -12
View File
@@ -104,8 +104,11 @@ python3 tools/npc_pipeline.py palette
```
`prepare` resizes the AI fabric tiles (kept locally in `assets/textures/npc/ai/`,
which is gitignored) onto the `cloth`/`pants` albedos; `build` bakes them into the
`.glb` and regenerates the procedural normal + packed ORM maps. `palette` writes
which is gitignored) onto the 256² `cloth`/`pants` albedos; `build` bakes them into
the `.glb` and regenerates the procedural normal + packed ORM maps at the same
256² size. Runtime fabric materials repeat the weave 4× (`NPC_FABRIC_UV_SCALE`)
and skip the per-character baked AO, which otherwise magnified into large
blotches; skin, hair and fixed gear keep AO. `palette` writes
`assets/textures/npc/palette.json` using `model_pipeline.dominant_colors`.
## Blender asset library (loaded by the game)
@@ -232,17 +235,39 @@ godot --path . --script res://src/test/capture_npc_baseline.gd -- \
python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
```
`capture_realism.gd` runs seven stages (day, portrait, evening, late, Joke Shop day, Joke Shop night, studio);
`--stages=day,late` limits it while tuning; `--flashlight` turns on the guard lamp for
night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one NPC on open sidewalk
and frames the face, which is the only capture that can judge avatar detail
(`--portrait-type=N` picks a role). The generated realism runs under
`capture_realism.gd` runs nine stages in this order: day, Hollywood surface close-ups,
portrait, evening, late, Joke Shop day, Joke Shop surface close-ups, Joke Shop night,
studio; `--stages=day,late` limits it while tuning; `--flashlight` turns on the guard
lamp for night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one
NPC on open sidewalk and frames the face, which is the only capture that can judge
avatar detail (`--portrait-type=N` picks a role). For the 0.9.5 texture audit, run
the surface stages directly; the HUD is hidden for them:
```bash
godot --path . --script res://src/test/capture_realism.gd \
-- --out=res://tmp/surfaces --stages=surfaces,surfaces_joke
```
The generated realism runs under
`docs/screens/realism/` are **local, gitignored QA artifacts**; commit only the
resulting decisions/docs, not the run folders. The texture-adoption run compared
the pre-adoption captures with the current build: on the shots both sets captured,
the judge's like-for-like means rose on every sub-score (overall 0.8 → 1.0,
backdrop 0.6 → 1.6) and the Pillow metric gap fell 0.444 → 0.348. The character
pass added the portrait shot and recorded the new faces, hands and sleeve cuffs.
resulting decisions/docs, not the run folders. The 0.9.5 texture/UI audit checked actual screen sizes: all screen art is
1280×720, access cards are 256×128 and HUD icons are 64×64; no runtime texture
was oversized or unused. The 24-NPC smoke now reports 168 meshes / 131,616
triangles / 6.94 ms. Compressed payloads are 24.55 MB of surface picks, 22.67 MB
of screen art and 0.48 MB of NPC maps.
The 0.9.5 realism judge ran once per shot with `z-ai/glm-5.3-flash`
(`python3 tools/scene_concepts.py realism --run docs/screens/realism/2026-11-02-tex
--judge`). Its mean `after` scores were overall 1.8, lighting 2.3, materials 1.8,
texture detail 1.2, backdrop 2.0 and characters 1.2; the Pillow metric gap was
0.306. The judge still ranks the painted concept targets above the stylized build,
particularly lit night facades and dense backdrop detail. That is expected: the
pass validated close-range surfaces and promoted the texture repairs, not a
photoreal renderer. The 0.9.5 before/after close-up sheet is
[`blender_previews/sheet_texture_refinement_0.9.5.jpg`](blender_previews/sheet_texture_refinement_0.9.5.jpg);
the texture-adoption run itself was the earlier 0.8-style candidate comparison,
and its report remains local/gitignored.
The Forward+ A/B compared the Compatibility entrance with the optional Desktop HQ
preset. The gear-fit run checked the sunglasses, bag and helmet against measured
head/skirt bounds with `python3 tools/blender_assets.py fit`; it also fixed the
+4 -2
View File
@@ -90,8 +90,10 @@ All eight use the `npc_base` contract, so each one can replace
`test_character_joints_line_up_with_npc_base` checks this within 1 cm, so the
animation proxies in `_build_skinned_body()` stay valid.
- material slots `skin`, `cloth`, `pants`, `accessory`, `hair` carry the same
neutral `assets/textures/npc/<slot>_{albedo,normal}.png` maps, so
`Materials.npc_slot()` tints them the same way it tints `npc_base`.
neutral `assets/textures/npc/<slot>_{albedo,normal}.png` maps at 256², so
`Materials.npc_slot()` tints them the same way it tints `npc_base`. Cloth and
pants repeat the weave 4× (`NPC_FABRIC_UV_SCALE`) and intentionally skip the
per-character baked AO; skin, hair and fixed gear keep it.
- budget ≤ **8,000 triangles and ≤ 8,000 glTF vertices** for the eight library
variants (vertex count after seam splits; `CHAR_MAX_TRIS`/`CHAR_MAX_VERTS` in
`tools/blender_assets.py`, raised from 6,000/5,000 on 2026-11-02). The frozen
+3
View File
@@ -52,6 +52,9 @@ Committed contact sheets and local Blender renders for the character, gear and p
| <img src="sheet_npc_hands_compare.jpg" width="280" alt="sheet_npc_hands_compare.jpg"> | [`sheet_npc_hands_compare.jpg`](sheet_npc_hands_compare.jpg) |
| <img src="sheet_npc_hands_grip_compare.jpg" width="280" alt="sheet_npc_hands_grip_compare.jpg"> | [`sheet_npc_hands_grip_compare.jpg`](sheet_npc_hands_grip_compare.jpg) |
| <img src="sheet_npc_baseline_compare-shoes.jpg" width="280" alt="sheet_npc_baseline_compare-shoes.jpg"> | [`sheet_npc_baseline_compare-shoes.jpg`](sheet_npc_baseline_compare-shoes.jpg) |
| <img src="sheet_npc_baseline_compare-tex256.jpg" width="280" alt="sheet_npc_baseline_compare-tex256.jpg"> | [`sheet_npc_baseline_compare-tex256.jpg`](sheet_npc_baseline_compare-tex256.jpg) |
| <img src="sheet_npc_baseline_compare-texuv.jpg" width="280" alt="sheet_npc_baseline_compare-texuv.jpg"> | [`sheet_npc_baseline_compare-texuv.jpg`](sheet_npc_baseline_compare-texuv.jpg) |
| <img src="sheet_texture_refinement_0.9.5.jpg" width="280" alt="sheet_texture_refinement_0.9.5.jpg"> | [`sheet_texture_refinement_0.9.5.jpg`](sheet_texture_refinement_0.9.5.jpg) |
| <img src="sheet_npc_gear.jpg" width="280" alt="sheet_npc_gear.jpg"> | [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) |
| <img src="sheet_props.jpg" width="280" alt="sheet_props.jpg"> | [`sheet_props.jpg`](sheet_props.jpg) |
| <img src="shoulder_bag.png" width="280" alt="shoulder_bag.png"> | [`shoulder_bag.png`](shoulder_bag.png) |
Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

+8 -2
View File
@@ -5,7 +5,7 @@ Implemented so far:
- Godot 4.7.2 project scaffold, input map, procedural world builder (CI/local/release unified)
- Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`
- Three authored scenarios (El Capitan Entertainment Centre / Hollywood Boulevard, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools
- Headless unit tests: **262 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings)
- Headless unit tests: **263 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings)
- Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event
- First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review
- Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`)
@@ -42,6 +42,12 @@ Implemented so far:
- NPC neck/shoulder transition (0.9.4 work): the body graph now uses a fuller neck base, a slightly tapered neck top and a lower shoulder root, giving the head/neck/shoulder line a trapezius slope instead of a thin post. The full character library was rebuilt with a final degenerate-face cleanup; the baseline comparison is `sheet_npc_baseline_compare-body.jpg`.
- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
- NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`.
- Release v0.9.5 (2026-11-02): the tagged commit `4bc6390` passed Gitea Actions run 922 (checks + full build) and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.5>.
- Release v0.9.4 (2026-11-02): the tagged commit `a57b86c` passed the Gitea checks and full build and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4>.
- Environment texture pass (0.9.5 work): `capture_realism.gd` now has hidden-HUD `surfaces`/`surfaces_joke` close-up stages. The Walk of Fame band uses the paver texture as dark terrazzo, the neighbour awning keeps its cream stripe, and the skyline shader blends its lower 42 m toward a per-phase haze colour. Brick, asphalt, limestone and paver UV scales/profiles passed without replacement.
- Texture/prop audit (0.9.5 work): all 21 screen-art textures are 1280×720 and referenced; access cards are 256×128 and HUD icons 64×64. No oversized or unused runtime texture was found; the legacy root textures remain only as the texture-generator/asset-test fallback. The 24-NPC smoke now reports 168 meshes, 131,616 triangles and 6.94 ms (50 ms budget); compressed payload is 24.55 MB of surface picks, 22.67 MB of screen art and 0.48 MB of NPC maps.
- NPC texture pass (0.9.5 work): the NPC base and all eight library characters now use 256² albedo/normal/ORM maps instead of 128². Cloth and pants use `NPC_FABRIC_UV_SCALE` (4× weave) and skip the baked per-character AO, removing the large blotchy patches in portraits; skin, hair and gear keep baked AO. The base and eight GLBs plus AO maps were rebuilt; the 24-NPC smoke remains green.
- Texture visual verification (0.9.5 work): the nine-stage realism run used `z-ai/glm-5.3-flash` once per shot; mean `after` scores were overall 1.8, lighting 2.3, materials 1.8, texture detail 1.2, backdrop 2.0 and characters 1.2, with a Pillow metric gap of 0.306. The judge remains an optional review aid, not a CI gate. The committed before/after close-up sheet is [`blender_previews/sheet_texture_refinement_0.9.5.jpg`](blender_previews/sheet_texture_refinement_0.9.5.jpg); it records that the paver/Walk of Fame and NPC fabric repairs are visible, while asphalt, limestone and skyline picks held without a replacement. `tools/check_screenshots.sh` passed 21 good / 0 bad and the screen-design judge passed 21/21 match with 0 review.
- NPC visual optimization (0.9.4, complete 2026-11-02): all eight variants passed the portrait/readability, proportion, gear, foot/shoe, headgear and final-context gates. The library uses an 8,000-triangle / 8,000-glTF-vertex cap (up from 6,000/5,000; `npc_base` fallback remains 6,000/4,000); current characters measure 4,482–5,474 triangles and 4,260–5,494 vertices. The 48-frame baseline and 96-frame final-context review pass, the hand/gear offset and shoes are in, and the accepted stylized limits are documented in `npc-visual-review.md`.
- NPC face-detail pass (0.9.4 work): ala wings and a smaller nose bridge/tip, one rounded mouth slot, and a darker ear concha; all eight rebuilt. The comparison is `sheet_npc_baseline_compare-face-detail.jpg`.
- NPC headgear clearance (0.9.4 work): the 96-frame final-context review after the face-detail pass confirms the cap, firefighter helmet, hood and sunglasses clear the refined brow and eyes; no gear rebuild was needed.
@@ -71,6 +77,6 @@ Implemented so far:
- Keyboard/gamepad focus pass (0.9.3 work): `UiFocus` rings wrap through visible/enabled controls, hidden Continue and disabled timeline actions are skipped, each modal has a default focus, and A/B are bound to `ui_accept`/`ui_cancel` for pad-only navigation. Recorded in `docs/ui-copy.md`.
- Button-state pass (0.9.3 work): a shared `UiStyles` helper now gives every action button explicit normal, hover, pressed, focus and disabled styles; the timeline disables Previous/Next at its boundaries, while hidden continue states and the keyboard-only tutorial skip remain intentional. Recorded in `docs/ui-copy.md`.
- Dialogue pass (0.9.3 work): NPC reason answers now use the authored `reason` context, with an in-character answer for every shared archetype; Hollywood and Studio gained door-staff lines; radio cancellation, tutorial Ask terminology, help, start hints and campaign reset copy were clarified. Audit recorded in `docs/ui-copy.md`.
- Pre-build NPC review (2026-11-02): all eight library characters were staged at portrait, conversation and crowd distance on Hollywood and the Joke Shop, in day and night lighting, with the normal crowd hidden (`src/test/capture_npc_review.gd`). No P0 issues were found. Two P1 gear-fit issues were fixed with runtime attach offsets: the authorized-guest lanyard badge now clears the jacket and the firefighter helmet sits above the eyes. Backpack straps, caps, hands and the intentionally low-poly faces were accepted for the stylized target; close-range flashlight wash remains an optional-lighting follow-up. The 24-NPC budget stayed at 6.93 ms, the gallery passed 13 good / 0 bad and the screen-design judge stayed 13/13, and a clean portrait scored a 0.732 vision gap against the photoreal concept.
- Pre-build NPC review (2026-11-02): all eight library characters were staged at portrait, conversation and crowd distance on Hollywood and the Joke Shop, in day and night lighting, with the normal crowd hidden (`src/test/capture_npc_review.gd`). No P0 issues were found. Two P1 gear-fit issues were fixed with runtime attach offsets: the authorized-guest lanyard badge now clears the jacket and the firefighter helmet sits above the eyes. Backpack straps, caps, hands and the intentionally low-poly faces were accepted for the stylized target; close-range flashlight wash remains an optional-lighting follow-up. The 24-NPC budget stayed at 6.94 ms, the gallery passed 13 good / 0 bad and the screen-design judge stayed 13/13, and a clean portrait scored a 0.732 vision gap against the photoreal concept.
Current gaps for the 0.x release line: deeper authored content/dialogue, animation and audio production, localization/accessibility expansion, 24-NPC/platform profiling, external playtesting, and signed/notarized release packaging. The full gap analysis and milestone plan live in [`TODO.md`](../TODO.md); the current architecture and documentation ownership map are in [`docs/architecture.md`](architecture.md).
+9 -4
View File
@@ -93,7 +93,7 @@ pass every applicable item before a library rebuild is accepted.
authored grip points.
8. **Crowd and performance:** the silhouette remains readable at the 8 m crowd
frame and the LOD/impostor tiers match the full mesh; the 24-NPC budget stays
within the existing 6.93 ms baseline (no regression beyond the measured
within the existing 6.94 ms baseline (no regression beyond the measured
variance), and no new per-frame material or mesh churn is introduced.
Asset-space `fit` values are measurements of the untouched GLBs, not a pass/fail
@@ -195,6 +195,8 @@ The refit backpack measures 380 triangles / 406 vertices.
shaping is the accepted stylized construction.
- The >26 m impostor remains a two-quad billboard; the LOD tier/hysteresis and
near/far swap are covered by `test_avatar_mesh_budget_and_lod_swap`.
- NPC fabric textures are 256² with a 4× UV repeat; cloth/pants skip the baked
per-character AO to avoid large portrait blotches. Skin, hair and gear keep AO.
- Evening readability is represented by the day/night/LATE final-context set;
LATE is the venue night variant.
@@ -222,11 +224,14 @@ The face-detail pass is [`sheet_npc_baseline_compare-face-detail.jpg`](blender_p
The hand pass is [`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg);
the forward-grip pass is
[`sheet_npc_hands_grip_compare.jpg`](blender_previews/sheet_npc_hands_grip_compare.jpg).
Keep the baseline sheet as the before artifact and add a new comparison sheet
rather than replacing it.
The 0.9.5 texture pass adds
[`sheet_texture_refinement_0.9.5.jpg`](blender_previews/sheet_texture_refinement_0.9.5.jpg),
which pairs the close-range surface and cloth comparisons. Keep the baseline
sheet as the before artifact and add a new comparison sheet rather than
replacing it.
## Next implementation order
0.9.4 is closed. The next visual work belongs to the 0.9.5 texture pass and the
0.9.4 and the 0.9.5 texture pass are closed. The next visual work belongs to the
0.10.0 acceptance review; keep the NPC baseline/final-context commands as the
regression gate for any future character edit.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 KiB

After

Width:  |  Height:  |  Size: 543 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 659 KiB

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 KiB

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 761 KiB

After

Width:  |  Height:  |  Size: 761 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 567 KiB

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 830 KiB

After

Width:  |  Height:  |  Size: 841 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+1 -1
View File
@@ -12,7 +12,7 @@ config_version=5
config/name="Gig Line"
config/description="Workplace security simulation about observation, access verification, and proportionate escalation."
config/version="0.9.4"
config/version="0.9.5"
run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.7", "GL Compatibility")
+6
View File
@@ -13,6 +13,10 @@ extends RefCounted
## asphalt replaced the flat first-generation tiles. Normal maps carry the mortar,
## paver joints and aggregate that Compatibility cannot get from screen space.
const PICK_DIR := "res://assets/textures/ai/game_ready/v2/picks/"
## The 256² fabric maps are authored for a fine weave; repeating them 4×
## across the body keeps the pattern readable instead of magnifying one cell
## into a large blotch.
const NPC_FABRIC_UV_SCALE := Vector3(4.0, 4.0, 1.0)
const T_BRICK := preload(PICK_DIR + "brick_facade.png")
const T_CONCRETE := preload(PICK_DIR + "sidewalk_pavers.png")
const T_ASPHALT := preload(PICK_DIR + "asphalt.png")
@@ -49,6 +53,8 @@ static func npc_slot(source: Material, tint: Color) -> StandardMaterial3D:
if material == null:
material = StandardMaterial3D.new()
material.albedo_color = tint
if material.resource_name in ["cloth", "pants"]:
material.uv1_scale = NPC_FABRIC_UV_SCALE
return material
## Wire a baked AO map (red channel) into every surface of an imported mesh.
+4 -1
View File
@@ -483,7 +483,10 @@ func _apply_outfit_materials(npc_type: int, outfit: Dictionary, cloth: Color,
if not tints.has(slot):
continue
var material := Materials.npc_slot(source, tints[slot])
if ao != null:
# The per-character baked AO is applied to skin, hair and fixed gear.
# On the 256² fabric maps it magnified into large patches at portrait
# distance, so cloth/pants keep their ORM weave and rely on lighting.
if ao != null and slot not in ["cloth", "pants"]:
material.ao_enabled = true
material.ao_texture = ao
material.ao_texture_channel = BaseMaterial3D.TEXTURE_CHANNEL_RED
+26 -7
View File
@@ -17,10 +17,14 @@ const T_SKYLINE_NIGHT := preload(PICK_DIR + "skyline_night.png")
## Height of each backdrop plane (metres); the bases sit on the ground plane.
const SKYLINE_HEIGHT := 90.0
const SKYLINE_BY_PHASE := {
Rules.Phase.EARLY: {"texture": T_SKYLINE_DAY, "tint": Color(0.98, 1.0, 1.04)},
Rules.Phase.MIDDAY: {"texture": T_SKYLINE_DAY, "tint": Color(1.02, 1.04, 1.08)},
Rules.Phase.EVENING: {"texture": T_SKYLINE_DUSK, "tint": Color(0.9, 0.92, 1.0)},
Rules.Phase.LATE: {"texture": T_SKYLINE_NIGHT, "tint": Color(1.5, 1.55, 1.65)},
Rules.Phase.EARLY: {"texture": T_SKYLINE_DAY, "tint": Color(0.98, 1.0, 1.04),
"haze": Color(0.62, 0.71, 0.82), "haze_strength": 0.36},
Rules.Phase.MIDDAY: {"texture": T_SKYLINE_DAY, "tint": Color(1.02, 1.04, 1.08),
"haze": Color(0.66, 0.75, 0.85), "haze_strength": 0.34},
Rules.Phase.EVENING: {"texture": T_SKYLINE_DUSK, "tint": Color(0.9, 0.92, 1.0),
"haze": Color(0.55, 0.43, 0.40), "haze_strength": 0.30},
Rules.Phase.LATE: {"texture": T_SKYLINE_NIGHT, "tint": Color(1.5, 1.55, 1.65),
"haze": Color(0.07, 0.09, 0.14), "haze_strength": 0.22},
}
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street
@@ -648,8 +652,10 @@ func _build_sunset_neighbours() -> void:
# Ground-floor shopfront and a red canvas awning.
_add_box(west, Vector3(8.6, 2.4, 0.12), Vector3(-22.6, 1.5, west_front + 0.08),
Color(0.08, 0.10, 0.12), false, null, Vector2.ONE, "glass")
# Keep the cream stripe: tinting the whole atlas dark red collapsed the
# canvas into a flat band at portrait distance.
_add_box(west, Vector3(9.2, 0.22, 1.3), Vector3(-22.6, 3.0, west_front + 0.65),
Color(0.48, 0.10, 0.08), true, T_AWNING, Vector2(3, 0.5), "awning")
Color(0.62, 0.62, 0.62), true, T_AWNING, Vector2(4, 1), "awning")
# Balcony rails on the upper floor.
for x in [-24.8, -21.2, -17.6]:
_add_box(west, Vector3(2.4, 0.08, 0.85), Vector3(x, 5.6, west_front + 0.55),
@@ -1379,8 +1385,10 @@ func _build_walk_of_fame() -> void:
var walk := Node3D.new()
walk.name = "WalkOfFame"
root.add_child(walk)
# Reuse the paver map as a dark terrazzo: a flat colour read as a black
# strip at close range, while the texture supplies the small aggregate.
_add_box(walk, Vector3(30.0, 0.035, 1.75), Vector3(0, 0.198, 9.15),
Color(0.20, 0.18, 0.17), false, null, Vector2.ONE, "stone")
Color(0.34, 0.30, 0.27), false, T_CONCRETE, Vector2(18, 1.05), "concrete")
var star_mesh := _star_mesh(0.40, 0.17)
var brass := Materials.flat(Color(0.70, 0.55, 0.24), "metal")
var coral := Materials.flat(Color(0.72, 0.30, 0.28), "stone")
@@ -1595,6 +1603,8 @@ func _build_skyline_backdrop() -> void:
mat.shader = shader
mat.set_shader_parameter("sky_texture", SKYLINE_BY_PHASE[_phase]["texture"])
mat.set_shader_parameter("tint", SKYLINE_BY_PHASE[_phase]["tint"])
mat.set_shader_parameter("haze_color", SKYLINE_BY_PHASE[_phase]["haze"])
mat.set_shader_parameter("haze_strength", SKYLINE_BY_PHASE[_phase]["haze_strength"])
mat.set_shader_parameter("repeats", 2.0)
_skyline_mat = mat
var mesh := PlaneMesh.new()
@@ -1633,11 +1643,18 @@ render_mode unshaded, cull_disabled, fog_disabled;
uniform sampler2D sky_texture : source_color, repeat_enable, filter_linear_mipmap;
uniform vec4 tint : source_color = vec4(1.0);
uniform vec4 haze_color : source_color = vec4(0.62, 0.71, 0.82, 1.0);
uniform float haze_strength = 0.36;
uniform float repeats = 2.0;
void fragment() {
vec4 panorama = texture(sky_texture, vec2(UV.x * repeats, UV.y));
ALBEDO = panorama.rgb * tint.rgb;
// Atmospheric perspective: blend the lower part of the panorama toward
// the horizon colour so the towers sit in the distance instead of reading
// as a crisp cardboard cut-out.
float world_y = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).y;
float horizon = clamp(1.0 - world_y / 42.0, 0.0, 1.0);
ALBEDO = mix(panorama.rgb * tint.rgb, haze_color.rgb, horizon * haze_strength);
ALPHA = panorama.a * tint.a;
ALPHA_SCISSOR_THRESHOLD = 0.5;
}
@@ -2241,6 +2258,8 @@ func set_phase(phase: int) -> void:
if _skyline_mat != null:
_skyline_mat.set_shader_parameter("sky_texture", entry["texture"])
_skyline_mat.set_shader_parameter("tint", entry["tint"])
_skyline_mat.set_shader_parameter("haze_color", entry["haze"])
_skyline_mat.set_shader_parameter("haze_strength", entry["haze_strength"])
var night: bool = phase == Rules.Phase.EVENING or phase == Rules.Phase.LATE
set_night_mode(night)
if _street_mat != null:
+58 -13
View File
@@ -62,6 +62,18 @@ const SHOTS_JOKE_DAY := [
const SHOTS_STUDIO := [
{"name": "studio_door", "pos": Vector3(0, 0.1, 6.2), "yaw": 0.0, "pitch": 2.0},
]
## Surface close-ups for the 0.9.5 texture audit; HUD is hidden for this stage.
const SHOTS_SURFACES := [
{"name": "surface_pavers", "pos": Vector3(0, 0.1, 10.5), "yaw": 0.0, "pitch": -55.0},
{"name": "surface_asphalt", "pos": Vector3(0, 0.1, 14.0), "yaw": 180.0, "pitch": -42.0},
{"name": "surface_limestone", "pos": Vector3(0, 0.1, 2.0), "yaw": 0.0, "pitch": 8.0},
{"name": "surface_facade_wide", "pos": Vector3(-2.0, 0.1, 13.2), "yaw": 12.0, "pitch": 20.0},
]
const SHOTS_SURFACES_JOKE := [
{"name": "surface_awning", "pos": Vector3(-22.6, 0.1, 2.6), "yaw": 0.0, "pitch": 14.0},
{"name": "surface_canopy", "pos": Vector3(0, 0.1, 7.5), "yaw": 0.0, "pitch": 24.0},
{"name": "surface_pavers_joke", "pos": Vector3(0, 0.1, 10.0), "yaw": 0.0, "pitch": -55.0},
]
## A face-on portrait: staged relative to a live NPC by `_stage_portrait()`.
const SHOTS_PORTRAIT := [
{"name": "npc_portrait", "pos": Vector3.ZERO, "yaw": 0.0, "pitch": 0.0,
@@ -69,13 +81,16 @@ const SHOTS_PORTRAIT := [
]
## Stage list: shift time selects the phase, `venue` switches the scenario first.
const STAGES := [
{"name": "day", "elapsed": 0.0, "venue": "", "shots": SHOTS_DAY, "night": false},
# Portrait next, while the day crowd is still on the street.
{"name": "portrait", "elapsed": 0.0, "venue": "", "shots": SHOTS_PORTRAIT,
{"name": "day", "elapsed": 0.0, "venue": "hollywood_building", "shots": SHOTS_DAY, "night": false},
{"name": "surfaces", "elapsed": 0.0, "venue": "hollywood_building", "shots": SHOTS_SURFACES, "night": false},
# Portrait before the venue switch: re-staging a close-up after a rebuild is
# not reliable, and this keeps the day crowd on Hollywood.
{"name": "portrait", "elapsed": 0.0, "venue": "hollywood_building", "shots": SHOTS_PORTRAIT,
"night": false},
{"name": "evening", "elapsed": 620.0, "venue": "", "shots": SHOTS_EVENING, "night": true},
{"name": "late", "elapsed": 760.0, "venue": "", "shots": SHOTS_LATE, "night": true},
{"name": "evening", "elapsed": 620.0, "venue": "hollywood_building", "shots": SHOTS_EVENING, "night": true},
{"name": "late", "elapsed": 760.0, "venue": "hollywood_building", "shots": SHOTS_LATE, "night": true},
{"name": "joke_shop_day", "elapsed": 0.0, "venue": "joke_shop", "shots": SHOTS_JOKE_DAY, "night": false},
{"name": "surfaces_joke", "elapsed": 0.0, "venue": "joke_shop", "shots": SHOTS_SURFACES_JOKE, "night": false},
{"name": "joke_shop", "elapsed": 700.0, "venue": "joke_shop", "shots": SHOTS_JOKE, "night": true},
{"name": "studio", "elapsed": 700.0, "venue": "tv_studio", "shots": SHOTS_STUDIO, "night": true},
]
@@ -166,6 +181,7 @@ func _process(delta: float) -> bool:
return false
func _begin_stage() -> void:
_clear_capture_pause()
_stage += 1
while _stage < STAGES.size() and not _only_stages.is_empty() \
and not _only_stages.has(str(STAGES[_stage]["name"])):
@@ -182,6 +198,7 @@ func _begin_stage() -> void:
gm.hud.clear_log()
gm.hud.set_target_label("")
gm.hud.set_action_hint("")
gm.hud.visible = not str(stage["name"]).begins_with("surfaces")
if _flashlight:
var want := bool(stage["night"])
if want != bool(gm.flashlight_on):
@@ -194,6 +211,7 @@ func _begin_stage() -> void:
print("capture_realism: stage %s at %d ms" % [stage["name"], Time.get_ticks_msec()])
func _run_queue(delta: float) -> void:
_clear_capture_pause()
if _queue.is_empty():
_phase = "stage"
return
@@ -226,9 +244,18 @@ func _run_queue(delta: float) -> void:
_queue.pop_front()
_shot_frames = 0
## Capture staging must not inherit a pause overlay from the previous stage or a
## window-focus change; a paused frame hides the very surfaces the pass audits.
func _clear_capture_pause() -> void:
if gm == null:
return
var pause = gm.get("pause_overlay")
if pause != null and pause.visible:
gm.call("_close_pause")
## Frame a nearby NPC from the front, with the sun behind the camera so the face
## is lit. The subject's own yaw is re-written every frame by its FSM, so the
## portrait turns the visual `Rig` child instead and stands on the sunlit side.
## is lit. The subject's FSM and idle head-turn are frozen so the still keeps the
## face toward the lens; photo/film poses are skipped because their props cover it.
func _stage_portrait(delta: float, angle_deg: float = 0.0) -> bool:
var guard_pos: Vector3 = gm.guard.global_position
var light := _sun_direction()
@@ -239,7 +266,17 @@ func _stage_portrait(delta: float, angle_deg: float = 0.0) -> bool:
var npc := node as Node3D
if npc == null or _faces_covered(npc):
continue
if _portrait_type >= 0 and int(_profile_of(npc).get("npc_type")) != _portrait_type:
var profile = _profile_of(npc)
if profile == null:
continue
if _portrait_type >= 0 and int(profile.get("npc_type")) != _portrait_type:
continue
# A film/photo pose holds a camera or phone in front of the face; keep the
# default portrait on an idle person so the texture pass sees skin.
var pose := ""
if npc.has_method("theater_pose"):
pose = str(npc.call("theater_pose"))
if _portrait_type < 0 and pose in ["photo", "film", "perform"]:
continue
var distance: float = npc.global_position.distance_to(guard_pos)
if distance > 25.0:
@@ -265,9 +302,10 @@ func _stage_portrait(delta: float, angle_deg: float = 0.0) -> bool:
continue
other.set_physics_process(false)
other.global_position += Vector3(0.0, 0.0, 140.0)
# Freeze the subject: its FSM re-writes the yaw every physics tick, which
# would turn it away from the camera again.
# Freeze the subject: its FSM re-writes the yaw every physics tick, and the
# idle head-turn would still look away after the body is aimed at the camera.
best.set_physics_process(false)
best.set_process(false)
# Clear standing spot on the sidewalk, away from the queue: the close-up is a
# character still, not a slice of the scene, and the crowd would block it.
var spot := Vector3(0.0, 0.0, 0.0)
@@ -275,6 +313,11 @@ func _stage_portrait(delta: float, angle_deg: float = 0.0) -> bool:
spot = gm.guard.get("spawn_position")
best.global_position = spot + _portrait_spot
best.rotation.y = 0.0
var subject_head := best.get_node_or_null("Rig/Head") as Node3D
if subject_head == null:
subject_head = best.find_child("Head", true, false) as Node3D
if subject_head != null:
subject_head.rotation.y = 0.0
# Stand between the subject and the sun, so the face is front-lit.
var to_light := -light
to_light = to_light.rotated(Vector3.UP, deg_to_rad(angle_deg))
@@ -290,10 +333,12 @@ func _stage_portrait(delta: float, angle_deg: float = 0.0) -> bool:
# Face the camera: from the subject, the guard lies in the -look direction.
# Use the drawn rig so an FSM yaw on the root cannot fight the pose.
var rig := best.find_child("Rig", true, false) as Node3D
# NPC art faces +Z; yaw the root toward the guard so the face, not the
# back, is under the camera.
var face := atan2(-look.x, -look.z)
best.rotation.y = face
if rig != null:
rig.rotation.y = atan2(-look.x, -look.z)
else:
best.rotation.y = atan2(-look.x, -look.z)
rig.rotation.y = 0.0
return true
+12 -3
View File
@@ -42,10 +42,19 @@ func _process(delta: float) -> bool:
if frame >= WARMUP_FRAMES + SAMPLE_FRAMES:
var average := sample_ms / SAMPLE_FRAMES
var meshes := 0
var triangles := 0
for avatar in avatars:
meshes += avatar.find_children("*", "MeshInstance3D", true, false).size()
print("NPC budget: %d avatars, %d meshes, avg frame %.2f ms (budget %.0f ms)" % [
COUNT, meshes, average, BUDGET_MS])
for node in avatar.find_children("*", "MeshInstance3D", true, false):
var mi := node as MeshInstance3D
if mi == null or mi.mesh == null:
continue
meshes += 1
for surface in mi.mesh.get_surface_count():
var arrays := mi.mesh.surface_get_arrays(surface)
var indices: PackedInt32Array = arrays[Mesh.ARRAY_INDEX]
triangles += indices.size() / 3
print("NPC budget: %d avatars, %d meshes, %d triangles, avg frame %.2f ms (budget %.0f ms)" % [
COUNT, meshes, triangles, average, BUDGET_MS])
if average > BUDGET_MS:
printerr("SMOKE FAIL: frame time %.1f ms exceeds the %.0f ms budget" % [
average, BUDGET_MS])
+28
View File
@@ -3,6 +3,7 @@ extends RefCounted
## styles, queue slots, LOD tiers/labels, and the articulated rig structure.
const NpcAvatar := preload("res://src/game/npc_avatar.gd")
const Materials := preload("res://src/game/materials.gd")
const NpcProfile := preload("res://src/core/npc_profile.gd")
const Rules := preload("res://src/core/rules.gd")
@@ -333,6 +334,33 @@ func test_skinned_outfit_tints_the_material_slots() -> bool:
avatar.free()
return a and b and c and d
func test_fabric_uv_scale_avoids_the_baked_ao_pattern() -> bool:
var avatar := _avatar(Rules.NpcType.TOURIST, 44)
var mi := avatar.find_child("npc_base", true, false) as MeshInstance3D
var fabric := 0
var skin_ao := false
var problems: Array[String] = []
for index in mi.mesh.get_surface_count():
var source := mi.mesh.surface_get_material(index)
var material := mi.get_surface_override_material(index) as StandardMaterial3D
if source == null or material == null:
continue
var slot := str(source.resource_name)
if slot in ["cloth", "pants"]:
fabric += 1
if not material.uv1_scale.is_equal_approx(
Materials.NPC_FABRIC_UV_SCALE):
problems.append("%s uv=%s" % [slot, str(material.uv1_scale)])
if material.ao_enabled:
problems.append("%s still uses baked AO" % slot)
elif slot == "skin" and material.ao_enabled:
skin_ao = true
var a := TestUtils.check(fabric > 0 and problems.is_empty(),
"cloth/pants use the fine-UV scale and skip baked AO (%s)" % str(problems))
var b := TestUtils.check(skin_ao, "skin keeps its baked AO")
avatar.free()
return a and b
func test_forced_fallback_still_builds_the_procedural_rig() -> bool:
var profile := NpcProfile.new()
profile.id = "fallback_test"
+13 -4
View File
@@ -113,8 +113,13 @@ func test_hollywood_uses_the_real_landmark_layout() -> bool:
var banners := root.find_child("EventBanners", true, false)
var d := TestUtils.check(banners != null and banners.get_child_count() >= 9,
"three red event banners with their trim are present")
var e := TestUtils.check(root.find_child("WalkOfFame", true, false) != null,
"the Hollywood Walk of Fame strip is built")
var walk := root.find_child("WalkOfFame", true, false)
var walk_band := walk.get_child(0) as MeshInstance3D if walk != null else null
var walk_material: Material = walk_band.mesh.surface_get_material(0) if walk_band != null else null
var e := TestUtils.check(walk != null and walk_material != null
and walk_material is StandardMaterial3D
and (walk_material as StandardMaterial3D).albedo_texture != null,
"the Hollywood Walk of Fame strip is built with the terrazzo texture")
var f := TestUtils.check(root.get_node_or_null("NeighbourBlocks/ElCapitanTheatre") != null
and root.get_node_or_null("NeighbourBlocks/EastRetailBlock") != null,
"the El Capitan Theatre and east retail neighbours are built")
@@ -260,8 +265,12 @@ func test_skyline_backdrop_uses_alpha() -> bool:
var mat: ShaderMaterial = builder._skyline_mat
var b := TestUtils.check(mat != null and mat.shader != null, "skyline shader material captured")
var code := mat.shader.code if mat != null and mat.shader != null else ""
var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled"),
"backdrop shader is alpha-scissored and fog-free")
var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled")
and code.contains("haze_strength"),
"backdrop shader is alpha-scissored, fog-free and haze-blended")
var haze := float(mat.get_shader_parameter("haze_strength")) if mat != null else 0.0
var c2 := TestUtils.check(haze > 0.0, "skyline haze is enabled")
c = c and c2
var day: Texture2D = mat.get_shader_parameter("sky_texture")
builder.set_phase(Rules.Phase.LATE)
var night: Texture2D = mat.get_shader_parameter("sky_texture")
+1 -1
View File
@@ -46,7 +46,7 @@ BONES = [
]
MAX_TRIANGLES = 6000
MAX_VERTICES = 4000
TEX_SIZE = 128
TEX_SIZE = 256
AI_DIR = TEX_DIR / "ai"
# Base material slots that get an AI fabric albedo (`prepare` layers them over
# the procedural weave). Outer layers (jacket / vest / coat / dress) have no