Close the 0.9.3 UI polish milestone
Add deterministic captures 14-21 for the remaining screen-design states, fix the panel/button layout issues they exposed, and record 21/21 compare and judge results. Bump the source to 0.9.3 and move the completed milestone to DONE.md.
No files matched your search
@@ -4,7 +4,14 @@ All notable user-facing changes to Gig Line. The format follows
|
|||||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
|
||||||
Semantic Versioning.
|
Semantic Versioning.
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -1660,3 +1660,78 @@ redesign.
|
|||||||
blocks a build.
|
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.
|
Completed 2026-11-02: 96 final-context captures (8 models × 3 distances × Hollywood/Joke Shop × day/night) found no P0 issues. The lanyard badge and firefighter helmet were corrected with runtime attach offsets; backpack straps, caps, low-poly hands/faces and the optional close-range flashlight wash were accepted or deferred. The 24-NPC budget is 6.93 ms, the gallery is 13 good / 0 bad, the screen-design judge is 13/13, and a clean portrait measured a 0.732 vision gap against the photoreal concept. No build, version bump or tag was created.
|
||||||
|
|
||||||
|
## Patch 0.9.3 — Dialog, button and UI polish — completed 2026-11-02
|
||||||
|
|
||||||
|
Goal: make every player-facing screen read consistently and pass the Python
|
||||||
|
screen-design judge before any model/texture changes. No gameplay rules change.
|
||||||
|
|
||||||
|
### Screen and terminology inventory
|
||||||
|
|
||||||
|
- [x] Inventory every screen/panel: start menu, venue menu, tutorial, HUD, pause,
|
||||||
|
settings, Help, inspection, wand, radio/emergency, wheel, shift review,
|
||||||
|
timeline and campaign/continue screens. Recorded in `docs/ui-copy.md`.
|
||||||
|
- [x] Record the canonical terms for admit/refuse, warning/blocking,
|
||||||
|
assistance/emergency, body camera, shift review and campaign; the current
|
||||||
|
inspection/wand/radio/review labels use them (see `docs/ui-copy.md`).
|
||||||
|
- [x] Audit all button labels for verb-first wording, capitalization, ordering,
|
||||||
|
destructive-action confirmation and consistent confirm/cancel placement;
|
||||||
|
recorded in `docs/ui-copy.md` and fixed Quit/Apply wording.
|
||||||
|
- [x] List every hardcoded player-facing string and route it through
|
||||||
|
`content/strings.en.json`; remaining UI literals are numeric/debug-only or
|
||||||
|
dynamic data. Placeholder validation covers the new keys.
|
||||||
|
- [x] Review NPC/door-staff/radio/tutorial/help/review/campaign dialogue for
|
||||||
|
repetition, ambiguity, tone and localization length. (Reason contexts,
|
||||||
|
door-staff lines, radio/tutorial/help copy and length findings recorded in
|
||||||
|
`docs/ui-copy.md`; longest dialogue line is 56 characters.)
|
||||||
|
|
||||||
|
### Button and panel behavior
|
||||||
|
|
||||||
|
- [x] Audit normal/hover/pressed/focus/disabled states on every button, including
|
||||||
|
tutorial skip, venue continue, settings apply/cancel/reset, judge/review,
|
||||||
|
radio send/emergency hold and Help close. (Shared `UiStyles` states applied;
|
||||||
|
timeline boundary disabling and the keyboard-only F1 skip are documented in
|
||||||
|
`docs/ui-copy.md`.)
|
||||||
|
- [x] Verify keyboard-only and gamepad-only navigation: focus order, wrapping,
|
||||||
|
default focus, confirm/cancel behavior and no focus traps. (Focus rings,
|
||||||
|
gamepad A/B UI bindings, default focus and per-modal cancel behavior are
|
||||||
|
recorded in `docs/ui-copy.md`.)
|
||||||
|
- [x] Verify every modal closes with Esc/right-click where documented, recaptures
|
||||||
|
the mouse, and does not leave the guard frozen or the menu visible. (Single
|
||||||
|
top-modal close path, menu/settings return handling and pointer recapture are
|
||||||
|
recorded in `docs/ui-copy.md`; session smoke covers menu Settings, Help
|
||||||
|
right-click and pause Settings.)
|
||||||
|
- [x] Test buttons at minimum/maximum HUD font scale and with 1.5–2× longer
|
||||||
|
strings; fix clipping, overflow and overlapping layouts. (Target chip now
|
||||||
|
measures/caps scaled text, HUD top labels clip, and action buttons ellipsize;
|
||||||
|
regression checks are in `test_accessibility.gd` and `test_ui_buttons.gd`.)
|
||||||
|
- [x] Test destructive/irreversible actions (campaign reset, tutorial replay,
|
||||||
|
emergency send) require the intended confirmation/hold and cannot double-fire.
|
||||||
|
(Reset/replay share one guarded confirmation dialog; emergency hold submits
|
||||||
|
once and ignores the post-submit release. Covered by session/radio tests.)
|
||||||
|
- [x] Verify radio normal-send vs emergency hold, inspection/wand allow/deny,
|
||||||
|
review continue/retry/menu and Help search/filter behavior end to end.
|
||||||
|
(`smoke_ui_flows.gd` covers inspection, radio, Help, wand and review;
|
||||||
|
`smoke_campaign.gd` covers review continue/checkpoint resume.)
|
||||||
|
|
||||||
|
### Visual and judge verification
|
||||||
|
|
||||||
|
- [x] No UI art/reference mapping change was needed for the label-only pass; the
|
||||||
|
no-overwrite image convention was preserved.
|
||||||
|
- [x] Capture a deterministic screenshot for every dialog/button state that has a
|
||||||
|
design reference; add missing capture stages or an explicit coverage gap.
|
||||||
|
(`src/test/capture_ui_states.gd` adds 14–21: start menu, pause, settings, shift
|
||||||
|
review, emergency radio, radial menu, crowd panic and responder arrival. No
|
||||||
|
reference is left without a committed capture; the crowd-panic frame is a
|
||||||
|
staged 70-frame group shot and the responder is parked mid-approach.)
|
||||||
|
- [x] Ran `tools/check_screenshots.sh` and `screen_designs.py compare`: 21 good,
|
||||||
|
0 bad.
|
||||||
|
- [x] Ran the judge with automatic model selection: 21/21 match, 0 review, 0 diff.
|
||||||
|
- [x] Recorded the judge model and result in `docs/screen-design-report.md`; the
|
||||||
|
generated captures remain local/gitignored.
|
||||||
|
- [x] Add a short UI copy/button checklist to `docs/smoke-checklist.md`.
|
||||||
|
- [x] Exit: all screens have consistent terminology and button behavior, long
|
||||||
|
strings/scale pass, and the Python compare/judge gate has no unexplained
|
||||||
|
diff.
|
||||||
|
|
||||||
|
Completed 2026-11-02: all 0.9.3 UI checks are closed. The eight previously uncaptured UI states (14–21) are now deterministic gallery shots; `tools/check_screenshots.sh` reports 21 good / 0 bad and the automatic judge 21/21 match, 0 diff. The source version is now 0.9.3; no release build or tag has been produced for it yet.
|
||||||
@@ -63,7 +63,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
release build, version bump or tag was created at this gate.
|
release build, version bump or tag was created at this gate.
|
||||||
- **UI flow smoke (2026-11-02):** `smoke_ui_flows.gd` boots the real scene and
|
- **UI flow smoke (2026-11-02):** `smoke_ui_flows.gd` boots the real scene and
|
||||||
exercises inspection allow/deny, normal vs emergency radio, Help filtering,
|
exercises inspection allow/deny, normal vs emergency radio, Help filtering,
|
||||||
wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.3/0.9.6
|
wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.6
|
||||||
handoff; it complements the unit tests that only exercise panels.
|
handoff; it complements the unit tests that only exercise panels.
|
||||||
- **Destructive actions (2026-11-02):** `SettingsPanel._open_destructive_dialog()`
|
- **Destructive actions (2026-11-02):** `SettingsPanel._open_destructive_dialog()`
|
||||||
owns one confirmation instance for reset/replay; repeated presses focus the
|
owns one confirmation instance for reset/replay; repeated presses focus the
|
||||||
@@ -91,16 +91,16 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
states and font colours for the dark session/UI shell. Use `UiStyles.apply_button()`
|
states and font colours for the dark session/UI shell. Use `UiStyles.apply_button()`
|
||||||
for every new `Button`; CheckButton/OptionButton keep the engine theme. Timeline
|
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.
|
Previous/Next disable at the ends; F1 tutorial skip is keyboard-only by design.
|
||||||
- **Version and roadmap renumber (2026-11-02):** source is now **0.9.2**; the
|
- **Version and roadmap renumber (2026-11-02):** source is now **0.9.3**; the
|
||||||
completed map/NPC work is the patch content. Postponed work is renumbered to
|
completed map/NPC work and UI polish are the patch content. Next work is
|
||||||
**0.9.3 UI**, **0.9.4 NPC**, **0.9.5 textures**, **0.9.6 integration**, then the
|
**0.9.4 NPC**, **0.9.5 textures**, **0.9.6 integration**, then the **0.10.0**
|
||||||
**0.10.0** minor release candidate. `TODO.md` plans only 0.x patch/minor
|
minor release candidate. `TODO.md` plans only 0.x patch/minor increments; no
|
||||||
increments; no major-version scope is defined yet. No release build or tag has been produced
|
major-version scope is defined yet. No release build or tag has been produced
|
||||||
for 0.9.2.
|
for 0.9.3.
|
||||||
- **Priority change (2026-11-02):** the map optimization (originally planned for 0.9.5) and the pre-build
|
- **Priority change (2026-11-02):** the map optimization, pre-build NPC revisit and
|
||||||
NPC revisit are complete and in `DONE.md`. The postponed 0.9.3 UI, 0.9.4 NPC
|
0.9.3 UI polish are complete and in `DONE.md`. The 0.9.4 NPC detail, 0.9.5
|
||||||
detail, 0.9.5 textures and 0.9.6 integration/0.10.0 release work are next. The
|
textures and 0.9.6 integration/0.10.0 release work are next. The current source
|
||||||
current source version is 0.9.2; no release build or tag has been produced.
|
version is 0.9.3; no release build or tag has been produced.
|
||||||
- **Hollywood landmark map (2026-11-02):** the default venue is the real El Capitan
|
- **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
|
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
|
reference/mapping notes live in `docs/hollywood-boulevard-map.md`; the map-budget test
|
||||||
@@ -248,17 +248,19 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
material (`baseColorTexture` + `metallicRoughnessTexture`, NEAREST sampling). Colours are sRGB in
|
material (`baseColorTexture` + `metallicRoughnessTexture`, NEAREST sampling). Colours are sRGB in
|
||||||
the texture, so the old `srgb_to_linear`/`_linear_rgba` helpers were removed. `audit_glb` fails
|
the texture, so the old `srgb_to_linear`/`_linear_rgba` helpers were removed. `audit_glb` fails
|
||||||
if a prop is not exactly one material or misses TEXCOORD_0/images.
|
if a prop is not exactly one material or misses TEXCOORD_0/images.
|
||||||
- **Mockups complete (v0.3.2):** `docs/screens/designs/` holds AI references for all 19
|
- **Mockups complete (v0.3.2, updated 0.9.3):** `docs/screens/designs/` holds AI references for
|
||||||
catalogue screens. Only the six staged captures (01–06) are compared/judged; the other 13
|
all 21 catalogue screens, and every entry now has a committed current-build capture.
|
||||||
have no `capture`, so adding staged captures for the wand panel, timeline and escalation HUD
|
`capture_screenshots.gd` covers scenes 01–13, `capture_ui_states.gd` covers UI states 14–21,
|
||||||
would extend automated visual checks. `generate` now calls `_load_env_file()`.
|
and `capture_prop_gallery.gd` covers the prop gallery. `generate` now calls `_load_env_file()`.
|
||||||
- **Staged captures (v0.3.3, updated 2026-11-02):** `capture_screenshots.gd` emits the gallery
|
- **Staged captures (v0.3.3, updated 2026-11-02):** `capture_screenshots.gd` emits the scene
|
||||||
shots; states 7–10 switch to The Joke Shop at elapsed 620 s, retry the wand until it alerts,
|
gallery; states 7–10 switch to The Joke Shop at elapsed 620 s, retry the wand until it alerts,
|
||||||
then jump to 760 s after staging for night lighting. LATE now has its own routine pool.
|
then jump to 760 s after staging for night lighting. LATE now has its own routine pool.
|
||||||
Shot 12 closes any stale wand/inspection modal before the venue beauty shot (the wand fallback
|
Shot 12 closes any stale wand/inspection modal before the venue beauty shot (the wand fallback
|
||||||
can leave `hud.show_wand` open without `wand_open`). The timeline needs
|
can leave `hud.show_wand` open without `wand_open`). The timeline needs
|
||||||
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents. The escalation
|
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents.
|
||||||
HUD mockup is third-person now; `judge` still classes it `review` (no diff).
|
`capture_ui_states.gd` adds 14–21; crowd panic stages a group 70 frames into the flee and
|
||||||
|
responder arrival parks the responder with the door locked. `check_screenshots.sh` writes all
|
||||||
|
captures to `docs/screenshots/` before `compare`; judge is 21/21 match.
|
||||||
- **Prop collision (v0.4.0):** `_add_prop_model()` fits a `StaticBody3D` from the model bounds
|
- **Prop collision (v0.4.0):** `_add_prop_model()` fits a `StaticBody3D` from the model bounds
|
||||||
(cylinder for bollard/bin/hydrant, box otherwise) and adds a `NavigationObstacle3D`; NPC agents
|
(cylinder for bollard/bin/hydrant, box otherwise) and adds a `NavigationObstacle3D`; NPC agents
|
||||||
enable avoidance and move with the safe velocity from `velocity_computed`. The navmesh stays a
|
enable avoidance and move with the safe velocity from `velocity_computed`. The navmesh stays a
|
||||||
@@ -732,7 +734,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.
|
- Venue `door_staff_lines` are logged through `GameManager` (inspect / wand / refuse). They are not a separate dialogue UI.
|
||||||
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
|
- Access loadout is implemented; do not re-fix `override_access` as if it were still unread.
|
||||||
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
|
- `Save.SETTINGS_FILE` is declared and unused; settings persistence goes through `Save.load_settings()` / `save_settings()`.
|
||||||
- Headless suite is **261/261**; the windowed smokes, screenshot compare and judge pass. Remaining visual follow-ups are in `TODO.md`.
|
- Headless suite is **261/261**; Python tooling is **97/97**; screenshot compare is **21 good / 0 bad** and the automatic judge is **21/21 match, 0 diff**. Remaining visual follow-ups are in `TODO.md`.
|
||||||
- Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args).
|
- Typed spawn signal extra args: handlers must accept the 4th `event: Dictionary` or Godot will not call them (`test_shift_coordinator_runs_a_shift` failed until `_on_spawn` took four args).
|
||||||
- GDScript inference trap that broke the main scene: a `var` typed as a base class
|
- GDScript inference trap that broke the main scene: a `var` typed as a base class
|
||||||
(`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a
|
(`radial: CanvasLayer`) exposes script members as `Variant`, so `var x := radial.is_open` is a
|
||||||
@@ -742,8 +744,9 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
- Do not snapshot right after changing a transform in the same frame: `capture_screenshots.gd`
|
- Do not snapshot right after changing a transform in the same frame: `capture_screenshots.gd`
|
||||||
needs a state/frame gap or the capture uses the old camera orientation.
|
needs a state/frame gap or the capture uses the old camera orientation.
|
||||||
- `EventScheduler` probabilities are per simulated second, not per call. Never poll it per frame.
|
- `EventScheduler` probabilities are per simulated second, not per call. Never poll it per frame.
|
||||||
- Screenshot staging lives in `src/test/capture_screenshots.gd` (guard at z≈10.5 facing the door).
|
- Screenshot staging lives in `src/test/capture_screenshots.gd` (guard at z≈10.5 facing the door)
|
||||||
`docs/screenshots/` copies are manual: copy from `user://screenshots/`.
|
and `src/test/capture_ui_states.gd` (UI states 14–21). `tools/check_screenshots.sh` renders all
|
||||||
|
captures into `docs/screenshots/`; do not regenerate them by hand.
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
@@ -754,10 +757,10 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
## Plan vs code
|
## Plan vs code
|
||||||
|
|
||||||
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
|
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
|
||||||
2026-11-01 the playable build covers all three venues, controls, access/radio/evidence/review,
|
2026-11-02 the playable build covers the three venues, tutorials/campaign/checkpoints, the 0.9.3
|
||||||
the Blender asset library, the realism pass, and the visual-alignment pass. `TODO.md` holds only
|
UI/accessibility pass, and the Blender + realism asset pipeline. `TODO.md` holds the remaining
|
||||||
the realism follow-ups and optional future ideas. The project uses a custom headless runner, not
|
0.9.4–0.10.0 work. The project uses a custom headless runner, not GUT, and Gitea Actions, not
|
||||||
GUT, and Gitea Actions, not GitHub Actions.
|
GitHub Actions.
|
||||||
|
|
||||||
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD
|
|||||||
|
|
||||||
## Versioning and Delivery
|
## Versioning and Delivery
|
||||||
|
|
||||||
`config/version` in `project.godot` is the single version source (currently `0.9.2`). Releases are tagged `v<version>`; the CI pipeline builds and publishes the standard artifacts plus the optional Desktop HQ zip. Version bumps follow [Semantic Versioning](https://semver.org/). See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands.
|
`config/version` in `project.godot` is the single version source (currently `0.9.3`). 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
|
## Design Direction
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,9 @@ precision walk, the Joke Shop's Sunset Strip frontage, the TV Studio arrival, an
|
|||||||
> **Latest published: v0.9.1** — unified Godot 4.7.2 CI/release environment, plus
|
> **Latest published: v0.9.1** — unified Godot 4.7.2 CI/release environment, plus
|
||||||
> v0.9.0's production-quality and v0.8 tutorial/campaign/resume work.
|
> v0.9.0's production-quality and v0.8 tutorial/campaign/resume work.
|
||||||
>
|
>
|
||||||
> **Current source: v0.9.2** — the Hollywood/Joke Shop map rebuilds and the
|
> **Current source: v0.9.3** — the UI/accessibility polish pass on top of the
|
||||||
> NPC final-context review; the release tag is pending.
|
> Hollywood/Joke Shop map rebuilds and the NPC final-context review; the release
|
||||||
|
> tag is pending.
|
||||||
> Releases are built automatically when a `vX.Y.Z` tag is pushed
|
> Releases are built automatically when a `vX.Y.Z` tag is pushed
|
||||||
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
|
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
|
||||||
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
|
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
|
||||||
@@ -125,11 +126,12 @@ Full setup, every run/test/smoke command, and the controls table are in
|
|||||||
|
|
||||||
## Development Status
|
## Development Status
|
||||||
|
|
||||||
v0.9.2 is the current source version: the Hollywood and Joke Shop map
|
v0.9.3 is the current source version: the completed UI/accessibility polish
|
||||||
rebuilds, the NPC final-context review and the HUD/wand string fixes. It builds
|
pass (dialogue, button states, focus/navigation, modal close, long-string layout,
|
||||||
on v0.9.1's Godot 4.7.2 baseline, v0.9.0's production-quality and
|
destructive-action guards and full screen capture coverage), on top of v0.9.2's
|
||||||
audio/accessibility pass, v0.8's tutorial/campaign/resume work and the v0.7.x
|
Hollywood/Joke Shop map rebuilds and NPC final-context review. It builds on
|
||||||
venue/gear/judge work. See [`docs/development-status.md`](docs/development-status.md)
|
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
|
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).
|
verification uses the [`docs/smoke-checklist.md`](docs/smoke-checklist.md).
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these
|
|||||||
|
|
||||||
## Gap Analysis and 0.x Roadmap
|
## Gap Analysis and 0.x Roadmap
|
||||||
|
|
||||||
Baseline: **v0.9.2** (2026-11-02). Three playable venues, tutorial/campaign/checkpoint resume,
|
Baseline: **v0.9.3** (2026-11-02). Three playable venues, tutorial/campaign/checkpoint resume,
|
||||||
difficulty/help, audio/accessibility settings, a 24-NPC budget baseline, a complete
|
difficulty/help, audio/accessibility settings, a 24-NPC budget baseline, a complete
|
||||||
escalation/access/radio loop, shift review, the Blender asset library, 261 headless + 97 Python
|
escalation/access/radio loop, shift review, the Blender asset library, 261 headless + 97 Python
|
||||||
tests, a clean screen-design judge and an optional Forward+ Desktop HQ build. This is a strong
|
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.
|
release-candidate foundation, not yet a 0.10.0 release candidate.
|
||||||
|
|
||||||
### Documentation audit (completed 2026-11-01)
|
### Documentation audit (completed 2026-11-01)
|
||||||
@@ -33,93 +33,24 @@ release-candidate foundation, not yet a 0.10.0 release candidate.
|
|||||||
| Area | Current baseline | Remaining gap | Risk | Milestone |
|
| Area | Current baseline | Remaining gap | Risk | Milestone |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| Content and balance | 3 venues with validated 12+ event matrices, recovery events and contextual dialogue | External playtest evidence, balance/fairness pass, event-weight tuning and final content lock | Repetition or unfair outcomes survive into the release candidate | 0.10.0 |
|
| Content and balance | 3 venues with validated 12+ event matrices, recovery events and contextual dialogue | External playtest evidence, balance/fairness pass, event-weight tuning and final content lock | Repetition or unfair outcomes survive into the release candidate | 0.10.0 |
|
||||||
| Dialog/UI | Complete screens and panel art, but copy/button behavior has not had a dedicated audit | Terminology, button states, long-string layout and full screen coverage need a focused pass | Inconsistent UI undermines first-hour clarity | 0.9.3 |
|
| Dialog/UI | Audited terminology, copy, button states, focus/navigation, modal close, long strings and all 21 screen captures | Keep the capture/compare/judge gate current as content changes | Low; the remaining risk is drift after new UI is added | Complete (0.9.3) |
|
||||||
| Animation/presentation | Procedural pose layers, portrait captures, reduced hem clipping | Thorough NPC silhouette/face/gear/pose/LOD optimization before visual QA | Close-up jank reduces perceived quality | 0.9.4 |
|
| Animation/presentation | Procedural pose layers, portrait captures, reduced hem clipping | Thorough NPC silhouette/face/gear/pose/LOD optimization before visual QA | Close-up jank reduces perceived quality | 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.9.3 / 0.10.0 |
|
| 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.9.3 / 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.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 |
|
||||||
| 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 |
|
| 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, optional judge | Full visual judge gate, release-candidate smoke matrix, final gallery and release dry run | Regressions escape the release gate | 0.9.6 / 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 |
|
| Release/legal | Unsigned desktop/macOS builds, Web/HQ packages | Signing/notarization decision, checksums, AI-asset provenance, credits, privacy/body-camera and rating review | Public release blocked by trust/policy | 0.10.0 |
|
||||||
| Documentation | Architecture/ADR/status/roadmap ownership established | Final README/changelog/status/TODO/smoke-checklist sign-off and known-limitations page | Docs drift at launch | 0.9.6 / 0.10.0 |
|
| Documentation | Architecture/ADR/status/roadmap ownership established | Final README/changelog/status/TODO/smoke-checklist sign-off and known-limitations page | Docs drift at launch | 0.9.6 / 0.10.0 |
|
||||||
| Future 0.x | Stable 0.x feature set | Live replay, more venues, VO/music, image-to-mesh props and seasonal content | Scope creep before release | 0.11.0+ |
|
| Future 0.x | Stable 0.x feature set | Live replay, more venues, VO/music, image-to-mesh props and seasonal content | Scope creep before release | 0.11.0+ |
|
||||||
|
|
||||||
**Current priority:** map optimization and the **pre-build NPC revisit** are
|
**Current priority:** map optimization, the **pre-build NPC revisit** and the
|
||||||
complete (both moved to `DONE.md` on 2026-11-02). **0.9.3 UI polish** is active;
|
**0.9.3 UI polish** pass are complete (all moved to `DONE.md` on 2026-11-02).
|
||||||
0.9.4 NPC-detail and 0.9.5 texture patches, then 0.9.6 integration and the 0.10.0
|
**0.9.4 NPC-detail** is active; 0.9.5 texture refinement, 0.9.6 integration and
|
||||||
release candidate, are queued. No release build or tag has been produced for the
|
the 0.10.0 release candidate are queued. The source version is now 0.9.3; no
|
||||||
current source version.
|
release build or tag has been produced for it.
|
||||||
|
|
||||||
## Patch 0.9.3 — Dialog, button and UI polish
|
## Patch 0.9.4 — NPC visual optimization
|
||||||
|
|
||||||
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.
|
|
||||||
- [ ] Capture a deterministic screenshot for every dialog/button state that has a
|
|
||||||
design reference; add missing capture stages or an explicit coverage gap.
|
|
||||||
- [x] Ran `tools/check_screenshots.sh` and `screen_designs.py compare`: 13 good,
|
|
||||||
0 bad.
|
|
||||||
- [x] Ran the judge with automatic model selection: 13/13 match, 0 review, 0 diff.
|
|
||||||
- [x] Recorded the judge model and result in `docs/screen-design-report.md`; the
|
|
||||||
generated captures remain local/gitignored.
|
|
||||||
- [ ] Add a short UI copy/button checklist to `docs/smoke-checklist.md`.
|
|
||||||
- [ ] Exit: all screens have consistent terminology and button behavior, long
|
|
||||||
strings/scale pass, and the Python compare/judge gate has no unexplained
|
|
||||||
diff.
|
|
||||||
|
|
||||||
## Postponed — Patch 0.9.4 — NPC visual optimization
|
|
||||||
|
|
||||||
Goal: make the eight character variants hold up in portrait, conversation and crowd
|
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
|
views without changing the `npc_base`/`Rig/Head` contract or the skinned/procedural
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ This document follows the structure of the arc42 architecture documentation temp
|
|||||||
- **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible.
|
- **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible.
|
||||||
- **PATCH** — bug fixes and small polish that do not change behavior.
|
- **PATCH** — bug fixes and small polish that do not change behavior.
|
||||||
|
|
||||||
The single source of truth for the version is `config/version` in `project.godot` (currently `0.9.2`). 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.3`). Each release is tagged in git as `v<version>` (e.g. `v0.6.0`). The `0.x` series is pre-stable, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md).
|
||||||
|
|
||||||
## 3. Context and Scope
|
## 3. Context and Scope
|
||||||
|
|
||||||
|
|||||||
@@ -261,6 +261,9 @@ python3 tools/screen_designs.py generate --draft --only wand --force
|
|||||||
# (requires OPENROUTER_API_KEY)
|
# (requires OPENROUTER_API_KEY)
|
||||||
python3 tools/screen_designs.py generate
|
python3 tools/screen_designs.py generate
|
||||||
|
|
||||||
|
# UI-state gallery (menu, pause, settings, review, radio, wheel, crowd, responder)
|
||||||
|
godot --path . --script res://src/test/capture_ui_states.gd -- --out=res://docs/screenshots
|
||||||
|
|
||||||
# Prop gallery (studio line-up) for close-range prop checks
|
# Prop gallery (studio line-up) for close-range prop checks
|
||||||
godot --path . --script res://src/test/capture_prop_gallery.gd
|
godot --path . --script res://src/test/capture_prop_gallery.gd
|
||||||
|
|
||||||
@@ -276,12 +279,11 @@ python3 tools/screen_designs.py judge --only 03- # one capture
|
|||||||
python3 tools/screen_designs.py judge --dry-run # build requests, no network
|
python3 tools/screen_designs.py judge --dry-run # build requests, no network
|
||||||
```
|
```
|
||||||
|
|
||||||
The catalogue covers 21 screens: the eleven README captures plus the start menu,
|
The catalogue covers 21 screens: the thirteen scene captures plus the start menu,
|
||||||
pause overlay, settings, shift review, radial menu, emergency radio, wand
|
pause overlay, settings, shift review, radial menu, emergency radio, crowd panic,
|
||||||
screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival,
|
and responder arrival. All 21 have an AI reference in `assets/images/ui/`
|
||||||
body-camera timeline, and the escalation HUD. All 21 have an AI reference in
|
(snapshot in `docs/screens/designs/`) and a committed capture, so every entry is
|
||||||
`assets/images/ui/` (snapshot in `docs/screens/designs/`); the eleven screens with
|
compared or judged against the running game.
|
||||||
staged captures are also compared or judged against the running game.
|
|
||||||
|
|
||||||
`compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank
|
`compare` flags gross drift: wrong aspect ratio, undersized frame, near-blank
|
||||||
frame, and large layout/palette/detail distance. It is a regression gate, not a
|
frame, and large layout/palette/detail distance. It is a regression gate, not a
|
||||||
@@ -295,8 +297,8 @@ Verdicts are classed as `match`, `review` (a mismatch below `--fail-confidence`,
|
|||||||
default 0.6 — expected when a concept mockup is more stylised than the game), or
|
default 0.6 — expected when a concept mockup is more stylised than the game), or
|
||||||
`diff` (a confident mismatch, which exits non-zero). Regenerate the designs after changing screen layout, then re-run
|
`diff` (a confident mismatch, which exits non-zero). Regenerate the designs after changing screen layout, then re-run
|
||||||
`compare` after regenerating the game screenshots with
|
`compare` after regenerating the game screenshots with
|
||||||
`src/test/capture_screenshots.gd`.
|
`src/test/capture_screenshots.gd` and `src/test/capture_ui_states.gd`.
|
||||||
|
|
||||||
For a one-command drift check, run `tools/check_screenshots.sh`. It renders the
|
For a one-command drift check, run `tools/check_screenshots.sh`. It renders the
|
||||||
staged captures and prop gallery into `docs/screenshots/` (using `xvfb-run` when
|
scene captures, UI-state captures and prop gallery into `docs/screenshots/` (using
|
||||||
no display is available) and then runs `screen_designs.py compare`.
|
`xvfb-run` when no display is available) and then runs `screen_designs.py compare`.
|
||||||
@@ -39,6 +39,7 @@ Implemented so far:
|
|||||||
- Aim-free selection pass (v0.3.0 work, [ADR-0011](adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work
|
- Aim-free selection pass (v0.3.0 work, [ADR-0011](adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work
|
||||||
|
|
||||||
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. The wand panel lists the guest's carried items, and multiline HUD/wand strings render real line breaks.
|
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. The wand panel lists the guest's carried items, and multiline HUD/wand strings render real line breaks.
|
||||||
|
- UI-state capture pass (0.9.3 work): `src/test/capture_ui_states.gd` adds committed captures 14–21 for the start menu, pause, settings, shift review, emergency radio, action wheel, crowd panic and responder arrival. Every one of the 21 catalogue entries now has a current-build capture; `tools/check_screenshots.sh` reports 21 good / 0 bad and the automatic judge reports 21/21 match, 0 diff. Crowd panic and responder arrival are staged presentation frames, not gameplay changes.
|
||||||
- Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a visible additive flashlight beam, and refreshed staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts.
|
- Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a visible additive flashlight beam, and refreshed staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts.
|
||||||
- Input/reliability fixes: selecting a venue releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. Mouse look now reads at `_input`, restores capture on window focus/click-back, and the HUD is mouse-transparent. Normal radio reports submit on click; emergency calls still require the hold-to-confirm path.
|
- Input/reliability fixes: selecting a venue releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. Mouse look now reads at `_input`, restores capture on window focus/click-back, and the HUD is mouse-transparent. Normal radio reports submit on click; emergency calls still require the hold-to-confirm path.
|
||||||
- Production-quality pass (v0.9.0): mixer/accessibility settings, audio volume clamping, 24-NPC budget smoke, shortened coat/dress stride, and a unified Godot 4.7.2 CI/local support baseline with documented upgrade/rollback requirements.
|
- Production-quality pass (v0.9.0): mixer/accessibility settings, audio volume clamping, 24-NPC budget smoke, shortened coat/dress stride, and a unified Godot 4.7.2 CI/local support baseline with documented upgrade/rollback requirements.
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
Date: 2026-11-02
|
Date: 2026-11-02
|
||||||
Model: automatic cheapest image+JSON selection (`z-ai/glm-5.3-flash` at run time)
|
Model: automatic cheapest image+JSON selection (`z-ai/glm-5.3-flash` at run time)
|
||||||
Command: `python3 tools/screen_designs.py judge`
|
Command: `python3 tools/screen_designs.py judge`
|
||||||
Result: **13/13 match, 0 review, 0 diff**
|
Result: **21/21 match, 0 review, 0 diff**
|
||||||
|
|
||||||
The screenshot gallery was regenerated on the current build before judging.
|
The screenshot gallery was regenerated on the current build before judging, including the eight UI-state captures 14–21 added in `src/test/capture_ui_states.gd`.
|
||||||
Accepted captures are in [`docs/screens/`](screens/README.md); the committed
|
Accepted captures are in [`docs/screens/`](screens/README.md); the committed
|
||||||
gallery is in [`docs/screenshots/`](screenshots/README.md).
|
gallery is in [`docs/screenshots/`](screenshots/README.md).
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 558 KiB After Width: | Height: | Size: 562 KiB |
|
Before Width: | Height: | Size: 757 KiB After Width: | Height: | Size: 761 KiB |
|
After Width: | Height: | Size: 537 KiB |
|
After Width: | Height: | Size: 671 KiB |
|
After Width: | Height: | Size: 479 KiB |
|
After Width: | Height: | Size: 529 KiB |
|
After Width: | Height: | Size: 464 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
@@ -1,6 +1,6 @@
|
|||||||
# Screenshots
|
# Screenshots
|
||||||
|
|
||||||
Committed gallery captures generated from the current build by src/test/capture_screenshots.gd and src/test/capture_prop_gallery.gd.
|
Committed gallery captures generated from the current build by src/test/capture_screenshots.gd, src/test/capture_ui_states.gd and src/test/capture_prop_gallery.gd.
|
||||||
|
|
||||||
## Image previews
|
## Image previews
|
||||||
|
|
||||||
@@ -19,3 +19,11 @@ Committed gallery captures generated from the current build by src/test/capture_
|
|||||||
| <img src="11-precision-walk.png" width="280" alt="11-precision-walk.png"> | [`11-precision-walk.png`](11-precision-walk.png) |
|
| <img src="11-precision-walk.png" width="280" alt="11-precision-walk.png"> | [`11-precision-walk.png`](11-precision-walk.png) |
|
||||||
| <img src="12-joke-shop.png" width="280" alt="12-joke-shop.png"> | [`12-joke-shop.png`](12-joke-shop.png) |
|
| <img src="12-joke-shop.png" width="280" alt="12-joke-shop.png"> | [`12-joke-shop.png`](12-joke-shop.png) |
|
||||||
| <img src="13-studio-arrival.png" width="280" alt="13-studio-arrival.png"> | [`13-studio-arrival.png`](13-studio-arrival.png) |
|
| <img src="13-studio-arrival.png" width="280" alt="13-studio-arrival.png"> | [`13-studio-arrival.png`](13-studio-arrival.png) |
|
||||||
|
| <img src="14-start-menu.png" width="280" alt="14-start-menu.png"> | [`14-start-menu.png`](14-start-menu.png) |
|
||||||
|
| <img src="15-pause-overlay.png" width="280" alt="15-pause-overlay.png"> | [`15-pause-overlay.png`](15-pause-overlay.png) |
|
||||||
|
| <img src="16-settings-panel.png" width="280" alt="16-settings-panel.png"> | [`16-settings-panel.png`](16-settings-panel.png) |
|
||||||
|
| <img src="17-shift-review.png" width="280" alt="17-shift-review.png"> | [`17-shift-review.png`](17-shift-review.png) |
|
||||||
|
| <img src="18-emergency-radio.png" width="280" alt="18-emergency-radio.png"> | [`18-emergency-radio.png`](18-emergency-radio.png) |
|
||||||
|
| <img src="19-radial-menu.png" width="280" alt="19-radial-menu.png"> | [`19-radial-menu.png`](19-radial-menu.png) |
|
||||||
|
| <img src="20-crowd-panic.png" width="280" alt="20-crowd-panic.png"> | [`20-crowd-panic.png`](20-crowd-panic.png) |
|
||||||
|
| <img src="21-responder-arrival.png" width="280" alt="21-responder-arrival.png"> | [`21-responder-arrival.png`](21-responder-arrival.png) |
|
||||||
@@ -47,3 +47,4 @@
|
|||||||
45. Esc and right-click close only the top panel (Help, Settings, Pause, Review, Radio, timeline, inspection/wand, action wheel); Settings opened from the venue menu returns to the menu, Help no longer opens Pause behind it, and closing a modal recaptures the mouse without leaving the guard frozen.
|
45. Esc and right-click close only the top panel (Help, Settings, Pause, Review, Radio, timeline, inspection/wand, action wheel); Settings opened from the venue menu returns to the menu, Help no longer opens Pause behind it, and closing a modal recaptures the mouse without leaving the guard frozen.
|
||||||
46. At 0.75× and 1.75× HUD font scale, long target names stay inside the target chip, the chip grows/caps correctly, and long button labels ellipsize instead of pushing panels off-screen.
|
46. At 0.75× and 1.75× HUD font scale, long target names stay inside the target chip, the chip grows/caps correctly, and long button labels ellipsize instead of pushing panels off-screen.
|
||||||
47. Reset campaign and Replay tutorial both ask for confirmation; a second click reuses the open dialog, Cancel does nothing, and OK fires once. Emergency send requires the full hold, sends exactly once, and does not send again on release.
|
47. Reset campaign and Replay tutorial both ask for confirmation; a second click reuses the open dialog, Cancel does nothing, and OK fires once. Emergency send requires the full hold, sends exactly once, and does not send again on release.
|
||||||
|
48. UI copy/button sweep: every panel uses the canonical admit/refuse, warning/blocking, assistance/emergency and body-camera terms; the start menu, pause, settings, Help, inspection, wand, radio, wheel, timeline and shift review all show readable hover/focus/pressed/disabled states; Tab/arrow focus wraps without landing on hidden or disabled actions; Esc/right-click closes only the top panel; and destructive reset/replay plus emergency send still require their confirmation or hold.
|
||||||
@@ -196,9 +196,34 @@ strings and the maximum supported HUD scale.
|
|||||||
`smoke_timeline.gd` covers review → timeline → review. These are end-to-end
|
`smoke_timeline.gd` covers review → timeline → review. These are end-to-end
|
||||||
scene smokes, not just panel-unit tests.
|
scene smokes, not just panel-unit tests.
|
||||||
|
|
||||||
|
## Visual coverage
|
||||||
|
|
||||||
|
`src/test/capture_ui_states.gd` completes the screen-design catalogue by staging
|
||||||
|
the states that the scene captures did not cover. The gallery now has one
|
||||||
|
deterministic current-build frame for every reference:
|
||||||
|
|
||||||
|
| State | Capture | Design reference |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Start / venue menu | `14-start-menu.png` | `start_menu.png` |
|
||||||
|
| Pause | `15-pause-overlay.png` | `pause_overlay.png` |
|
||||||
|
| Settings | `16-settings-panel.png` | `settings_panel.png` |
|
||||||
|
| Shift review | `17-shift-review.png` | `shift_review.png` |
|
||||||
|
| Emergency radio | `18-emergency-radio.png` | `emergency_radio.png` |
|
||||||
|
| Action wheel | `19-radial-menu.png` | `radial_menu.png` |
|
||||||
|
| Crowd panic | `20-crowd-panic.png` | `crowd_panic.png` |
|
||||||
|
| Responder arrival | `21-responder-arrival.png` | `responder_arrival.png` |
|
||||||
|
|
||||||
|
The crowd-panic frame is taken 70 frames after the panic event so the staged
|
||||||
|
group is visibly displaced from the steps toward the street; the responder is
|
||||||
|
parked on the approach with the door locked so the arrival/secure state is
|
||||||
|
readable. Both are presentation staging, not gameplay changes. `compare`
|
||||||
|
reports **21 good / 0 bad** and the automatic judge **21/21 match, 0 diff**.
|
||||||
|
The gallery is regenerated by `tools/check_screenshots.sh`.
|
||||||
|
|
||||||
## Remaining audit work
|
## Remaining audit work
|
||||||
|
|
||||||
- Player-facing labels are now routed through `content/strings.en.json`; numeric
|
- Player-facing labels are now routed through `content/strings.en.json`; numeric
|
||||||
values, empty-state clears and debug-only text remain in code by design.
|
values, empty-state clears and debug-only text remain in code by design.
|
||||||
- Focus/hover/pressed/disabled states and keyboard/gamepad rings are recorded above.
|
- Focus/hover/pressed/disabled states and keyboard/gamepad rings are recorded above.
|
||||||
- Capture every dialog/button state and run `screen_designs.py compare` plus the pinned judge.
|
- Pseudo-locale layout and a translator workflow remain scheduled for the
|
||||||
|
localization milestone, not this UI pass.
|
||||||
@@ -12,7 +12,7 @@ config_version=5
|
|||||||
|
|
||||||
config/name="Gig Line"
|
config/name="Gig Line"
|
||||||
config/description="Workplace security simulation about observation, access verification, and proportionate escalation."
|
config/description="Workplace security simulation about observation, access verification, and proportionate escalation."
|
||||||
config/version="0.9.2"
|
config/version="0.9.3"
|
||||||
run/main_scene="res://scenes/main.tscn"
|
run/main_scene="res://scenes/main.tscn"
|
||||||
config/features=PackedStringArray("4.7", "GL Compatibility")
|
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ func _init() -> void:
|
|||||||
var buttons := HBoxContainer.new()
|
var buttons := HBoxContainer.new()
|
||||||
vbox.add_child(buttons)
|
vbox.add_child(buttons)
|
||||||
action_button = Button.new()
|
action_button = Button.new()
|
||||||
|
action_button.custom_minimum_size = Vector2(190, 0)
|
||||||
action_button.text = Strings.lookup("ui.radio_send_assist")
|
action_button.text = Strings.lookup("ui.radio_send_assist")
|
||||||
action_button.button_down.connect(func(): _start_hold())
|
action_button.button_down.connect(func(): _start_hold())
|
||||||
action_button.button_up.connect(func(): _stop_hold(false))
|
action_button.button_up.connect(func(): _stop_hold(false))
|
||||||
@@ -114,6 +115,7 @@ func _init() -> void:
|
|||||||
UiStyles.apply_button(action_button, 16)
|
UiStyles.apply_button(action_button, 16)
|
||||||
buttons.add_child(action_button)
|
buttons.add_child(action_button)
|
||||||
cancel_button = Button.new()
|
cancel_button = Button.new()
|
||||||
|
cancel_button.custom_minimum_size = Vector2(140, 0)
|
||||||
cancel_button.text = Strings.lookup("ui.radio_cancel")
|
cancel_button.text = Strings.lookup("ui.radio_cancel")
|
||||||
cancel_button.pressed.connect(func(): cancelled.emit())
|
cancel_button.pressed.connect(func(): cancelled.emit())
|
||||||
UiStyles.apply_button(cancel_button, 16)
|
UiStyles.apply_button(cancel_button, 16)
|
||||||
|
|||||||
@@ -76,14 +76,25 @@ func _build() -> void:
|
|||||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
column.add_child(title)
|
column.add_child(title)
|
||||||
|
|
||||||
|
var scroll := ScrollContainer.new()
|
||||||
|
scroll.name = "SettingsScroll"
|
||||||
|
scroll.custom_minimum_size = Vector2(640, 500)
|
||||||
|
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
|
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
|
column.add_child(scroll)
|
||||||
|
var content := VBoxContainer.new()
|
||||||
|
content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
content.add_theme_constant_override("separation", 12)
|
||||||
|
scroll.add_child(content)
|
||||||
|
|
||||||
var sens_row := VBoxContainer.new()
|
var sens_row := VBoxContainer.new()
|
||||||
column.add_child(sens_row)
|
content.add_child(sens_row)
|
||||||
_sensitivity_value = _add_label_row(sens_row, "Mouse look sensitivity")
|
_sensitivity_value = _add_label_row(sens_row, "Mouse look sensitivity")
|
||||||
_sensitivity = _add_slider(sens_row, 0.0005, 0.0060, 0.0001)
|
_sensitivity = _add_slider(sens_row, 0.0005, 0.0060, 0.0001)
|
||||||
_sensitivity.value_changed.connect(_on_sensitivity_changed)
|
_sensitivity.value_changed.connect(_on_sensitivity_changed)
|
||||||
|
|
||||||
var font_row := VBoxContainer.new()
|
var font_row := VBoxContainer.new()
|
||||||
column.add_child(font_row)
|
content.add_child(font_row)
|
||||||
_font_scale_value = _add_label_row(font_row, "HUD font scale")
|
_font_scale_value = _add_label_row(font_row, "HUD font scale")
|
||||||
_font_scale = _add_slider(font_row, 0.75, 1.75, 0.05)
|
_font_scale = _add_slider(font_row, 0.75, 1.75, 0.05)
|
||||||
_font_scale.value_changed.connect(_on_font_scale_changed)
|
_font_scale.value_changed.connect(_on_font_scale_changed)
|
||||||
@@ -91,15 +102,15 @@ func _build() -> void:
|
|||||||
_invert_y = CheckButton.new()
|
_invert_y = CheckButton.new()
|
||||||
_invert_y.text = Strings.lookup("ui.settings_invert")
|
_invert_y.text = Strings.lookup("ui.settings_invert")
|
||||||
_invert_y.toggled.connect(_on_invert_y_toggled)
|
_invert_y.toggled.connect(_on_invert_y_toggled)
|
||||||
column.add_child(_invert_y)
|
content.add_child(_invert_y)
|
||||||
|
|
||||||
_head_bob = CheckButton.new()
|
_head_bob = CheckButton.new()
|
||||||
_head_bob.text = Strings.lookup("ui.settings_head_bob")
|
_head_bob.text = Strings.lookup("ui.settings_head_bob")
|
||||||
_head_bob.toggled.connect(_on_head_bob_toggled)
|
_head_bob.toggled.connect(_on_head_bob_toggled)
|
||||||
column.add_child(_head_bob)
|
content.add_child(_head_bob)
|
||||||
|
|
||||||
var diff_row := HBoxContainer.new()
|
var diff_row := HBoxContainer.new()
|
||||||
column.add_child(diff_row)
|
content.add_child(diff_row)
|
||||||
var diff_label := Label.new()
|
var diff_label := Label.new()
|
||||||
diff_label.text = Strings.lookup("ui.settings_difficulty")
|
diff_label.text = Strings.lookup("ui.settings_difficulty")
|
||||||
diff_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
diff_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
@@ -111,13 +122,13 @@ func _build() -> void:
|
|||||||
diff_row.add_child(_difficulty)
|
diff_row.add_child(_difficulty)
|
||||||
|
|
||||||
var audio_row := HBoxContainer.new()
|
var audio_row := HBoxContainer.new()
|
||||||
column.add_child(audio_row)
|
content.add_child(audio_row)
|
||||||
audio_row.add_child(_volume_label("Master volume"))
|
audio_row.add_child(_volume_label("Master volume"))
|
||||||
_master = _add_slider(audio_row, 0.0, 1.0, 0.05)
|
_master = _add_slider(audio_row, 0.0, 1.0, 0.05, 120.0)
|
||||||
audio_row.add_child(_volume_label("SFX"))
|
audio_row.add_child(_volume_label("SFX"))
|
||||||
_sfx = _add_slider(audio_row, 0.0, 1.0, 0.05)
|
_sfx = _add_slider(audio_row, 0.0, 1.0, 0.05, 120.0)
|
||||||
audio_row.add_child(_volume_label("Ambience"))
|
audio_row.add_child(_volume_label("Ambience"))
|
||||||
_ambience = _add_slider(audio_row, 0.0, 1.0, 0.05)
|
_ambience = _add_slider(audio_row, 0.0, 1.0, 0.05, 120.0)
|
||||||
_master.value_changed.connect(func(v): settings["master_volume"] = v)
|
_master.value_changed.connect(func(v): settings["master_volume"] = v)
|
||||||
_sfx.value_changed.connect(func(v): settings["sfx_volume"] = v)
|
_sfx.value_changed.connect(func(v): settings["sfx_volume"] = v)
|
||||||
_ambience.value_changed.connect(func(v): settings["ambience_volume"] = v)
|
_ambience.value_changed.connect(func(v): settings["ambience_volume"] = v)
|
||||||
@@ -125,15 +136,15 @@ func _build() -> void:
|
|||||||
_reduce_motion = CheckButton.new()
|
_reduce_motion = CheckButton.new()
|
||||||
_reduce_motion.text = Strings.lookup("ui.settings_reduce_motion")
|
_reduce_motion.text = Strings.lookup("ui.settings_reduce_motion")
|
||||||
_reduce_motion.toggled.connect(func(v): settings["reduce_motion"] = v)
|
_reduce_motion.toggled.connect(func(v): settings["reduce_motion"] = v)
|
||||||
column.add_child(_reduce_motion)
|
content.add_child(_reduce_motion)
|
||||||
_captions = CheckButton.new()
|
_captions = CheckButton.new()
|
||||||
_captions.text = Strings.lookup("ui.settings_captions")
|
_captions.text = Strings.lookup("ui.settings_captions")
|
||||||
_captions.toggled.connect(func(v): settings["captions_enabled"] = v)
|
_captions.toggled.connect(func(v): settings["captions_enabled"] = v)
|
||||||
column.add_child(_captions)
|
content.add_child(_captions)
|
||||||
|
|
||||||
var replay_row := HBoxContainer.new()
|
var replay_row := HBoxContainer.new()
|
||||||
replay_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
replay_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
column.add_child(replay_row)
|
content.add_child(replay_row)
|
||||||
var replay := Button.new()
|
var replay := Button.new()
|
||||||
replay.name = "ReplayTutorialButton"
|
replay.name = "ReplayTutorialButton"
|
||||||
replay.text = Strings.lookup("ui.settings_replay")
|
replay.text = Strings.lookup("ui.settings_replay")
|
||||||
@@ -151,12 +162,12 @@ func _build() -> void:
|
|||||||
controls_title.text = Strings.lookup("ui.settings_controls_help")
|
controls_title.text = Strings.lookup("ui.settings_controls_help")
|
||||||
controls_title.add_theme_font_size_override("font_size", 13)
|
controls_title.add_theme_font_size_override("font_size", 13)
|
||||||
controls_title.modulate = Color(0.75, 0.8, 0.9)
|
controls_title.modulate = Color(0.75, 0.8, 0.9)
|
||||||
column.add_child(controls_title)
|
content.add_child(controls_title)
|
||||||
var grid := GridContainer.new()
|
var grid := GridContainer.new()
|
||||||
grid.columns = 4
|
grid.columns = 4
|
||||||
grid.add_theme_constant_override("h_separation", 10)
|
grid.add_theme_constant_override("h_separation", 10)
|
||||||
grid.add_theme_constant_override("v_separation", 6)
|
grid.add_theme_constant_override("v_separation", 6)
|
||||||
column.add_child(grid)
|
content.add_child(grid)
|
||||||
for action in InputBindings.REMAPPABLE:
|
for action in InputBindings.REMAPPABLE:
|
||||||
var label := Label.new()
|
var label := Label.new()
|
||||||
label.text = str(action).replace("_", " ").capitalize()
|
label.text = str(action).replace("_", " ").capitalize()
|
||||||
@@ -207,12 +218,13 @@ func _volume_label(text: String) -> Label:
|
|||||||
label.custom_minimum_size = Vector2(82, 0)
|
label.custom_minimum_size = Vector2(82, 0)
|
||||||
return label
|
return label
|
||||||
|
|
||||||
func _add_slider(parent: Control, min_v: float, max_v: float, step: float) -> HSlider:
|
func _add_slider(parent: Control, min_v: float, max_v: float, step: float,
|
||||||
|
min_width: float = 420.0) -> HSlider:
|
||||||
var slider := HSlider.new()
|
var slider := HSlider.new()
|
||||||
slider.min_value = min_v
|
slider.min_value = min_v
|
||||||
slider.max_value = max_v
|
slider.max_value = max_v
|
||||||
slider.step = step
|
slider.step = step
|
||||||
slider.custom_minimum_size = Vector2(420, 24)
|
slider.custom_minimum_size = Vector2(min_width, 24)
|
||||||
parent.add_child(slider)
|
parent.add_child(slider)
|
||||||
return slider
|
return slider
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,14 @@ func _init() -> void:
|
|||||||
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
root.add_child(dim)
|
root.add_child(dim)
|
||||||
|
|
||||||
|
var center_host := CenterContainer.new()
|
||||||
|
center_host.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
root.add_child(center_host)
|
||||||
var center := VBoxContainer.new()
|
var center := VBoxContainer.new()
|
||||||
center.set_anchors_preset(Control.PRESET_CENTER)
|
center.custom_minimum_size = Vector2(760, 0)
|
||||||
center.alignment = BoxContainer.ALIGNMENT_CENTER
|
center.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
center.add_theme_constant_override("separation", 12)
|
center.add_theme_constant_override("separation", 8)
|
||||||
root.add_child(center)
|
center_host.add_child(center)
|
||||||
|
|
||||||
var title := Label.new()
|
var title := Label.new()
|
||||||
title.text = Strings.lookup("review.title")
|
title.text = Strings.lookup("review.title")
|
||||||
@@ -92,7 +95,7 @@ func _init() -> void:
|
|||||||
center.add_child(_quit_button)
|
center.add_child(_quit_button)
|
||||||
|
|
||||||
func _style_review_button(button: Button) -> void:
|
func _style_review_button(button: Button) -> void:
|
||||||
button.custom_minimum_size = Vector2(300, 52)
|
button.custom_minimum_size = Vector2(300, 46)
|
||||||
UiStyles.apply_button(button, 22)
|
UiStyles.apply_button(button, 22)
|
||||||
|
|
||||||
func show_review(review: Dictionary, scenario: Resource, history: Array = [],
|
func show_review(review: Dictionary, scenario: Resource, history: Array = [],
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ static func apply_button(button: Button, font_size: int = 0, compact: bool = fal
|
|||||||
button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||||
var margin_h := 12 if compact else 18
|
var margin_h := 12 if compact else 18
|
||||||
var margin_v := 7 if compact else 10
|
var margin_v := 7 if compact else 10
|
||||||
|
# clip_text removes the text-width contribution from the minimum size, so
|
||||||
|
# give unconstrained buttons a sane target width instead of collapsing them
|
||||||
|
# to the style margins (the radio action used to become a blank square).
|
||||||
|
if button.custom_minimum_size.x <= 0.0:
|
||||||
|
button.custom_minimum_size.x = 96.0 if compact else 160.0
|
||||||
var normal := _box(SURFACE, margin_h, margin_v)
|
var normal := _box(SURFACE, margin_h, margin_v)
|
||||||
var hover := _box(SURFACE_HOVER, margin_h, margin_v)
|
var hover := _box(SURFACE_HOVER, margin_h, margin_v)
|
||||||
var pressed := _box(SURFACE_PRESSED, margin_h, margin_v)
|
var pressed := _box(SURFACE_PRESSED, margin_h, margin_v)
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
extends SceneTree
|
||||||
|
## Capture deterministic screenshots for the UI states that have design
|
||||||
|
## references but no gallery shot yet: start menu, pause, settings, shift
|
||||||
|
## review, emergency radio, radial menu, crowd panic and responder arrival.
|
||||||
|
##
|
||||||
|
## Usage:
|
||||||
|
## godot --path . --script res://src/test/capture_ui_states.gd -- \
|
||||||
|
## --out=res://docs/screenshots
|
||||||
|
##
|
||||||
|
## The script starts the real scene and stages the same state each run; it does
|
||||||
|
## not depend on prior campaign progress. Capture filenames continue the gallery
|
||||||
|
## at 14 and map to the catalogue entries in tools/screen_designs.py.
|
||||||
|
|
||||||
|
const MAX_FRAMES := 6000
|
||||||
|
|
||||||
|
var out_dir := "user://screenshots"
|
||||||
|
var gm: Node = null
|
||||||
|
var frames := 0
|
||||||
|
var state := 0
|
||||||
|
var state_frames := 0
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
for arg in OS.get_cmdline_user_args():
|
||||||
|
if arg.begins_with("--out="):
|
||||||
|
out_dir = arg.trim_prefix("--out=")
|
||||||
|
root.size = Vector2i(1280, 720)
|
||||||
|
root.add_child(load("res://scenes/main.tscn").instantiate())
|
||||||
|
|
||||||
|
func _process(_delta: float) -> bool:
|
||||||
|
frames += 1
|
||||||
|
if gm == null:
|
||||||
|
gm = root.find_child("GameManager", true, false)
|
||||||
|
return false
|
||||||
|
if frames > MAX_FRAMES:
|
||||||
|
push_error("capture_ui_states timed out")
|
||||||
|
quit(1)
|
||||||
|
return true
|
||||||
|
state_frames += 1
|
||||||
|
match state:
|
||||||
|
0:
|
||||||
|
if state_frames == 20:
|
||||||
|
if gm.start_menu == null or not gm.start_menu.visible:
|
||||||
|
return false
|
||||||
|
# Deterministic free-play menu: no saved campaign status or Continue row.
|
||||||
|
gm.start_menu.open({})
|
||||||
|
gm.start_menu._continue_button.visible = false
|
||||||
|
gm.start_menu._status.text = ""
|
||||||
|
gm.start_menu._refresh_focus_targets()
|
||||||
|
gm.start_menu._focus_index(0)
|
||||||
|
if state_frames < 26:
|
||||||
|
return false
|
||||||
|
_shoot("14-start-menu.png")
|
||||||
|
gm.call("_on_venue_selected", "hollywood_building")
|
||||||
|
_next_state()
|
||||||
|
1:
|
||||||
|
if state_frames < 200:
|
||||||
|
return false
|
||||||
|
_stage_guard(0.0, 2.0)
|
||||||
|
gm.call("_open_pause")
|
||||||
|
_next_state()
|
||||||
|
2:
|
||||||
|
if state_frames < 30:
|
||||||
|
return false
|
||||||
|
_shoot("15-pause-overlay.png")
|
||||||
|
gm.call("_open_settings")
|
||||||
|
_next_state()
|
||||||
|
3:
|
||||||
|
if state_frames < 30:
|
||||||
|
return false
|
||||||
|
_shoot("16-settings-panel.png")
|
||||||
|
gm.settings_panel._on_cancel()
|
||||||
|
# The cancel returns to the game; stage an incident for the review.
|
||||||
|
if not gm.npcs.is_empty():
|
||||||
|
gm.call("_on_guard_target", gm.npcs[0])
|
||||||
|
gm.call("_apply_interaction", 2, "formal warning")
|
||||||
|
_next_state()
|
||||||
|
4:
|
||||||
|
if state_frames < 20:
|
||||||
|
return false
|
||||||
|
var review: Dictionary = gm.coordinator.build_review()
|
||||||
|
gm.shift_review_panel.show_review(review, gm.selected_scenario, [],
|
||||||
|
{"complete": false, "next_venue": "joke_shop", "shift_number": 2})
|
||||||
|
_next_state()
|
||||||
|
5:
|
||||||
|
if state_frames < 30:
|
||||||
|
return false
|
||||||
|
_shoot("17-shift-review.png")
|
||||||
|
gm.shift_review_panel.hide()
|
||||||
|
if gm.npcs.is_empty():
|
||||||
|
return false
|
||||||
|
gm.call("_on_guard_target", gm.npcs[0])
|
||||||
|
gm.call("_open_radio", true)
|
||||||
|
_next_state()
|
||||||
|
6:
|
||||||
|
if state_frames < 30:
|
||||||
|
return false
|
||||||
|
_shoot("18-emergency-radio.png")
|
||||||
|
gm.radio_panel.cancelled.emit()
|
||||||
|
if gm.npcs.is_empty():
|
||||||
|
return false
|
||||||
|
gm.call("_on_guard_target", gm.npcs[0])
|
||||||
|
gm.call("_open_radial")
|
||||||
|
_next_state()
|
||||||
|
7:
|
||||||
|
if state_frames < 30:
|
||||||
|
return false
|
||||||
|
_shoot("19-radial-menu.png")
|
||||||
|
gm.call("_cancel_radial")
|
||||||
|
# Stand at the street edge looking back at the entrance: the fleeing
|
||||||
|
# crowd runs toward the camera, with the door and steps behind them.
|
||||||
|
gm.guard.global_position = Vector3(0.0, 0.1, 14.0)
|
||||||
|
gm.guard.rotation.y = 0.0
|
||||||
|
gm.guard.head.rotation.x = deg_to_rad(2.0)
|
||||||
|
gm.hud.clear_log()
|
||||||
|
gm.hud.set_target_label("")
|
||||||
|
gm.hud.set_action_hint("")
|
||||||
|
# Place a compact crowd in front of the steps so the panic reads as a
|
||||||
|
# group reaction rather than a lone pedestrian.
|
||||||
|
var panic_spots := [
|
||||||
|
Vector3(-3.4, 0.1, 5.6), Vector3(-1.8, 0.1, 4.8),
|
||||||
|
Vector3(0.4, 0.1, 5.1), Vector3(2.0, 0.1, 4.6),
|
||||||
|
Vector3(3.4, 0.1, 5.4), Vector3(-2.6, 0.1, 7.0),
|
||||||
|
]
|
||||||
|
for i in mini(panic_spots.size(), gm.npcs.size()):
|
||||||
|
var staged: Node3D = gm.npcs[i]
|
||||||
|
staged.global_position = panic_spots[i]
|
||||||
|
staged.rotation.y = 0.0
|
||||||
|
gm.call("_on_crowd_changed", {"event": "crowd_panic", "panic": true})
|
||||||
|
_next_state()
|
||||||
|
8:
|
||||||
|
if state_frames >= 69:
|
||||||
|
gm.call("_clear_focus")
|
||||||
|
gm.hud.set_action_hint("")
|
||||||
|
if state_frames < 70:
|
||||||
|
return false
|
||||||
|
_shoot("20-crowd-panic.png")
|
||||||
|
gm.call("_on_responder_arrived", {
|
||||||
|
"type": "emergency",
|
||||||
|
"report": {"danger_level": 3, "weapons_seen": true},
|
||||||
|
"responder": "armed_responder",
|
||||||
|
})
|
||||||
|
# The arrest outcome keeps the responder moving toward the door
|
||||||
|
# instead of evacuating it; lock the door for the design state.
|
||||||
|
gm.door.set_locked(true)
|
||||||
|
if gm.world_builder:
|
||||||
|
gm.world_builder.set_door_locked(true)
|
||||||
|
gm.hud.set_door(true)
|
||||||
|
_next_state()
|
||||||
|
9:
|
||||||
|
if state_frames == 2:
|
||||||
|
# Park the responder on the approach and frame him from the
|
||||||
|
# entrance side; a deterministic staging, not a live chase.
|
||||||
|
for node in gm.npcs:
|
||||||
|
var c: Object = node.get_npc_controller() if node.has_method("get_npc_controller") else null
|
||||||
|
var is_responder: bool = node.name.begins_with("Responder") or (
|
||||||
|
c != null and c.profile != null and c.profile.id == "responder")
|
||||||
|
if is_responder:
|
||||||
|
node.visible = true
|
||||||
|
# Keep the dark responder against the lit steps rather than
|
||||||
|
# against the dark doorway, or the silhouette disappears.
|
||||||
|
node.global_position = Vector3(1.2, 0.1, 9.0)
|
||||||
|
node.rotation.y = 0.0
|
||||||
|
node.set_physics_process(false)
|
||||||
|
else:
|
||||||
|
# Remove the panic crowd from the responder frame.
|
||||||
|
node.visible = false
|
||||||
|
node.set_physics_process(false)
|
||||||
|
gm.guard.global_position = Vector3(0.0, 0.1, 14.0)
|
||||||
|
gm.guard.rotation.y = 0.0
|
||||||
|
gm.guard.head.rotation.x = deg_to_rad(2.0)
|
||||||
|
gm.hud.clear_log()
|
||||||
|
gm.call("_clear_focus")
|
||||||
|
if state_frames >= 29:
|
||||||
|
gm.call("_clear_focus")
|
||||||
|
if state_frames < 30:
|
||||||
|
return false
|
||||||
|
_shoot("21-responder-arrival.png")
|
||||||
|
quit(0)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _next_state() -> void:
|
||||||
|
state += 1
|
||||||
|
state_frames = 0
|
||||||
|
|
||||||
|
func _stage_guard(yaw_deg: float, pitch_deg: float) -> void:
|
||||||
|
if gm == null or gm.guard == null:
|
||||||
|
return
|
||||||
|
gm.guard.global_position = Vector3(0.0, 0.1, 10.5)
|
||||||
|
gm.guard.rotation.y = deg_to_rad(yaw_deg)
|
||||||
|
gm.guard.head.rotation.x = deg_to_rad(pitch_deg)
|
||||||
|
gm.hud.clear_log()
|
||||||
|
gm.hud.set_target_label("")
|
||||||
|
gm.hud.set_action_hint("")
|
||||||
|
|
||||||
|
func _shoot(file_name: String) -> void:
|
||||||
|
var dir := DirAccess.open(out_dir)
|
||||||
|
if dir == null:
|
||||||
|
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(out_dir))
|
||||||
|
var img: Image = root.get_texture().get_image()
|
||||||
|
var err := img.save_png(out_dir.path_join(file_name))
|
||||||
|
print("%s -> err=%d (%dx%d)" % [file_name, err, img.get_width(), img.get_height()])
|
||||||
@@ -25,7 +25,7 @@ func test_shared_button_states_are_complete() -> bool:
|
|||||||
"the disabled state is visually distinct from normal")
|
"the disabled state is visually distinct from normal")
|
||||||
button.text = "A very long localized action label that must not push the panel beyond the viewport"
|
button.text = "A very long localized action label that must not push the panel beyond the viewport"
|
||||||
var d := TestUtils.check(button.clip_text
|
var d := TestUtils.check(button.clip_text
|
||||||
and button.get_combined_minimum_size().x < 100.0,
|
and button.get_combined_minimum_size().x < 500.0,
|
||||||
"long action labels ellipsize instead of forcing a wide panel")
|
"long action labels ellipsize instead of forcing a wide panel")
|
||||||
button.free()
|
button.free()
|
||||||
return a and b and c and d
|
return a and b and c and d
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ if [[ -z "${DISPLAY:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
"${RUN[@]}" "$GODOT" --path . --script res://src/test/capture_screenshots.gd -- --out=res://docs/screenshots
|
"${RUN[@]}" "$GODOT" --path . --script res://src/test/capture_screenshots.gd -- --out=res://docs/screenshots
|
||||||
|
"${RUN[@]}" "$GODOT" --path . --script res://src/test/capture_ui_states.gd -- --out=res://docs/screenshots
|
||||||
"${RUN[@]}" "$GODOT" --path . --script res://src/test/capture_prop_gallery.gd -- --out=res://docs/screenshots
|
"${RUN[@]}" "$GODOT" --path . --script res://src/test/capture_prop_gallery.gd -- --out=res://docs/screenshots
|
||||||
python3 tools/screen_designs.py compare
|
python3 tools/screen_designs.py compare
|
||||||
@@ -228,7 +228,7 @@ SCREENS = [
|
|||||||
"kind": "ui",
|
"kind": "ui",
|
||||||
"title": "Radial action menu",
|
"title": "Radial action menu",
|
||||||
"source": "ADR-0009 · content/strings.en.json",
|
"source": "ADR-0009 · content/strings.en.json",
|
||||||
"ref": None,
|
"ref": "19-radial-menu.png",
|
||||||
"description": "A round doughnut menu centred on screen, 12 slices in "
|
"description": "A round doughnut menu centred on screen, 12 slices in "
|
||||||
"de-escalation order: Stop, Look, Point, No, Ask, Inspect, "
|
"de-escalation order: Stop, Look, Point, No, Ask, Inspect, "
|
||||||
"Wand, Warn, Block, Reason, Recognize, Radio. The focused "
|
"Wand, Warn, Block, Reason, Recognize, Radio. The focused "
|
||||||
@@ -250,7 +250,7 @@ SCREENS = [
|
|||||||
"kind": "ui",
|
"kind": "ui",
|
||||||
"title": "Shift review",
|
"title": "Shift review",
|
||||||
"source": "PLAN reputation / shift review · architecture.md ShiftReview",
|
"source": "PLAN reputation / shift review · architecture.md ShiftReview",
|
||||||
"ref": None,
|
"ref": "17-shift-review.png",
|
||||||
"description": "End-of-shift performance review: access decisions, "
|
"description": "End-of-shift performance review: access decisions, "
|
||||||
"escalation choices, incident list, battery and crowd "
|
"escalation choices, incident list, battery and crowd "
|
||||||
"outcomes, and reputation/score feedback.",
|
"outcomes, and reputation/score feedback.",
|
||||||
@@ -271,7 +271,7 @@ SCREENS = [
|
|||||||
"kind": "ui",
|
"kind": "ui",
|
||||||
"title": "Start menu",
|
"title": "Start menu",
|
||||||
"source": "MEMORY session shell (StartMenu, layer 20)",
|
"source": "MEMORY session shell (StartMenu, layer 20)",
|
||||||
"ref": None,
|
"ref": "14-start-menu.png",
|
||||||
"description": "The boot screen: a venue-selection menu (Hollywood "
|
"description": "The boot screen: a venue-selection menu (Hollywood "
|
||||||
"Boulevard, The Joke Shop, TV Studio) before a shift.",
|
"Boulevard, The Joke Shop, TV Studio) before a shift.",
|
||||||
"prompt": (
|
"prompt": (
|
||||||
@@ -290,7 +290,7 @@ SCREENS = [
|
|||||||
"kind": "ui",
|
"kind": "ui",
|
||||||
"title": "Pause overlay",
|
"title": "Pause overlay",
|
||||||
"source": "MEMORY session shell (PauseOverlay, layer 18)",
|
"source": "MEMORY session shell (PauseOverlay, layer 18)",
|
||||||
"ref": None,
|
"ref": "15-pause-overlay.png",
|
||||||
"description": "Pause menu: resume, restart, settings, and quit-to-menu.",
|
"description": "Pause menu: resume, restart, settings, and quit-to-menu.",
|
||||||
"prompt": (
|
"prompt": (
|
||||||
"Realistic 3D game pause menu overlay: buttons labelled Resume, "
|
"Realistic 3D game pause menu overlay: buttons labelled Resume, "
|
||||||
@@ -309,7 +309,7 @@ SCREENS = [
|
|||||||
"kind": "ui",
|
"kind": "ui",
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"source": "MEMORY session shell (SettingsPanel, layer 22)",
|
"source": "MEMORY session shell (SettingsPanel, layer 22)",
|
||||||
"ref": None,
|
"ref": "16-settings-panel.png",
|
||||||
"description": "Settings: look sensitivity and HUD font scale controls.",
|
"description": "Settings: look sensitivity and HUD font scale controls.",
|
||||||
"prompt": (
|
"prompt": (
|
||||||
"Realistic 3D game settings screen: sliders labelled look sensitivity "
|
"Realistic 3D game settings screen: sliders labelled look sensitivity "
|
||||||
@@ -325,7 +325,7 @@ SCREENS = [
|
|||||||
"kind": "ui",
|
"kind": "ui",
|
||||||
"title": "Emergency radio",
|
"title": "Emergency radio",
|
||||||
"source": "PLAN level 5 · strings radio.emergency_dispatched",
|
"source": "PLAN level 5 · strings radio.emergency_dispatched",
|
||||||
"ref": None,
|
"ref": "18-emergency-radio.png",
|
||||||
"description": "Hold-to-confirm emergency radio panel with a confirmation "
|
"description": "Hold-to-confirm emergency radio panel with a confirmation "
|
||||||
"prompt and a cooldown indicator.",
|
"prompt and a cooldown indicator.",
|
||||||
"prompt": (
|
"prompt": (
|
||||||
@@ -413,7 +413,7 @@ SCREENS = [
|
|||||||
"kind": "scene",
|
"kind": "scene",
|
||||||
"title": "Crowd panic",
|
"title": "Crowd panic",
|
||||||
"source": "PLAN.md Public Crowd Reaction · CrowdSystem.reaction_for_event",
|
"source": "PLAN.md Public Crowd Reaction · CrowdSystem.reaction_for_event",
|
||||||
"ref": None,
|
"ref": "20-crowd-panic.png",
|
||||||
"description": "After a panic event the sidewalk empties away from the "
|
"description": "After a panic event the sidewalk empties away from the "
|
||||||
"door: people running toward the street, one person still "
|
"door: people running toward the street, one person still "
|
||||||
"near the steps, the guard between them and the entrance.",
|
"near the steps, the guard between them and the entrance.",
|
||||||
@@ -434,7 +434,7 @@ SCREENS = [
|
|||||||
"kind": "scene",
|
"kind": "scene",
|
||||||
"title": "Responder arrival",
|
"title": "Responder arrival",
|
||||||
"source": "PLAN.md Level 5 Emergency Response · radio_system.gd responder dispatch",
|
"source": "PLAN.md Level 5 Emergency Response · radio_system.gd responder dispatch",
|
||||||
"ref": None,
|
"ref": "21-responder-arrival.png",
|
||||||
"description": "A dispatched responder walks up the steps toward a locked "
|
"description": "A dispatched responder walks up the steps toward a locked "
|
||||||
"door while the entrance is being cleared; blue light washes "
|
"door while the entrance is being cleared; blue light washes "
|
||||||
"the facade and the lock indicator reads red.",
|
"the facade and the lock indicator reads red.",
|
||||||
|
|||||||