Compare commits

..
2 Commits
Author SHA1 Message Date
zwitschi 3025ba1cef Capture the wand panel, escalation HUD and body-camera timeline
Full build / Import + headless tests (push) Successful in 35s
Full build / Full desktop build + optional release (push) Successful in 3m56s
The three newest catalogue screens had AI mockups but no staged capture, so
compare and judge skipped them. capture_screenshots.gd now continues past the
night set:

- switches to The Joke Shop at 620 s (EVENING still has routine spawns), frames a
  guest, and retries the wand until it alerts; if nobody carries an alerting item
  it stages the documented keys false-alarm case.
- applies a formal warning (arms the body camera) then blocking, jumps to 760 s
  for night lighting, and captures the HUD at L3.
- closes the open incident and opens the body-camera timeline, which now shows
  the warning / body-camera / blocking rows.

Wired the refs for wand_screening, escalation_hud and body_camera_timeline, and
regenerated the escalation mockup as a third-person over-the-shoulder night shot
so the reference matches the game camera. Added Hud.clear_log() to drop
target-cycling noise from staged shots. README gallery lists the three captures.

Verification: headless 182/182, Python tooling 53/53, compare 9 good / 0 bad,
judge: wand match 0.90, timeline match 1.00, escalation review, 0 diff.
Version 0.3.3.
2026-11-01 00:29:58 +01:00
zwitschi a5e7b3c013 Add AI mockups for the seven remaining catalogue screens
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Successful in 4m4s
The design catalogue grew to 19 screens, but only the original twelve had an AI
reference; the rest still showed "design -" in `list`. Generated the missing set
so every screen can be reviewed against an intended look:

- wand screening (metal-detected panel with carried items and allow/deny)
- Joke Shop queue, TV Studio arrival, crowd panic, responder arrival
- body-camera timeline, escalation HUD

`generate` now loads `.env` the same way `judge` does, so `--ai` no longer needs
the API key exported by hand. Each reference is 1280x720; the twelve existing
mockups were kept (no `--force`).

Verification: `list` reports 19 designs, Python tooling 53/53, headless suite
182/182, `compare` still 6 good / 0 bad on the captured screens. Version 0.3.2.
2026-11-01 00:01:02 +01:00
24 changed files with 166 additions and 21 deletions

No files matched your search

+31
View File
@@ -678,3 +678,34 @@ regenerated and `screen_designs.py compare` still reports 6 good / 0 bad.
- [x] Tests: atlas determinism and power-of-two sizing, UV range, palette override reaching the - [x] Tests: atlas determinism and power-of-two sizing, UV range, palette override reaching the
atlas, and a Godot test that each imported prop scene uses exactly one material. atlas, and a Godot test that each imported prop scene uses exactly one material.
- [x] Removed the now-unused sRGB→linear factor helpers (colours travel in the sRGB texture). - [x] Removed the now-unused sRGB→linear factor helpers (colours travel in the sRGB texture).
## Complete AI Mockup Set
Completed 2026-10-31 (v0.3.2). All 19 catalogue screens now have an AI reference; Python
tooling **53/53**.
- [x] Generated mockups for the seven entries that only had wireframes: wand screening,
Joke Shop queue, TV Studio arrival, crowd panic, responder arrival, body-camera
timeline, escalation HUD.
- [x] `generate` loads `.env` like `judge` already did, so `--ai` works without exporting
`OPENROUTER_API_KEY` by hand.
- [x] Verified every reference is 1280x720, reviewed the new images visually, and confirmed
`list` reports 19 designs (12 kept, 7 new).
## Staged Captures For The New Screens
Completed 2026-11-01 (v0.3.3). `screen_designs.py compare` reports **9 good / 0 bad**;
vision judge: wand **match** (0.90), timeline **match** (1.00), escalation **review**, 0 diff.
Python tooling **53/53**, headless **182/182**.
- [x] `capture_screenshots.gd` now continues past the night set: switches to The Joke Shop,
stages a guest, retries the wand until it alerts (falls back to the documented keys
false alarm), applies formal warning + blocking, and opens the body-camera timeline.
- [x] New captures `07-wand-screening.png`, `08-escalation-hud.png`, `09-body-camera-timeline.png`
added to `docs/screenshots/` and to the README gallery.
- [x] Wired `ref` for `wand_screening`, `escalation_hud` and `body_camera_timeline` so
`compare` and `judge` include them (9 of 19 screens now have a staged capture).
- [x] Regenerated the escalation HUD mockup as a third-person over-the-shoulder night shot so
the reference matches the game camera.
- [x] Added `Hud.clear_log()` so staged captures drop target-cycling noise; the escalation shot
keeps the real reaction lines.
+9
View File
@@ -37,6 +37,15 @@ 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
catalogue screens. Only the six staged captures (01–06) are compared/judged; the other 13
have no `capture`, so adding staged captures for the wand panel, timeline and escalation HUD
would extend automated visual checks. `generate` now calls `_load_env_file()`.
- **Staged captures (v0.3.3):** `capture_screenshots.gd` now emits nine shots. States 7–10 switch
to The Joke Shop at elapsed 620 s (EVENING has routine spawns; LATE has none), retry the wand
until it alerts, then jump to 760 s after staging for night lighting. The timeline needs
`recorder.close_all_open()`, because `to_dict()` only lists closed incidents. The escalation
HUD mockup is third-person now; `judge` still classes it `review` (no diff).
- **Backlog complete:** every planned TODO section is done and moved to `DONE.md`; `TODO.md` - **Backlog complete:** every planned TODO section is done and moved to `DONE.md`; `TODO.md`
now only lists optional future ideas (live 3D replay, NPC LOD, more venues, real audio, prop now only lists optional future ideas (live 3D replay, NPC LOD, more venues, real audio, prop
atlas, AI mockups for the new catalogue entries, image-to-mesh props). GUT was consciously not atlas, AI mockups for the new catalogue entries, image-to-mesh props). GUT was consciously not
+8 -4
View File
@@ -13,8 +13,10 @@ The central design goal is simple:
| ![Venue overview: the Hollywood Boulevard entrance with steps, sidewalks, street lamps, and signage](docs/screenshots/01-venue-overview.png) | ![Targeting an approaching NPC with the interaction HUD](docs/screenshots/02-targeting-npc.png) | | ![Venue overview: the Hollywood Boulevard entrance with steps, sidewalks, street lamps, and signage](docs/screenshots/01-venue-overview.png) | ![Targeting an approaching NPC with the interaction HUD](docs/screenshots/02-targeting-npc.png) |
| ![Access inspection: wristband/credential check with allow/deny prompt](docs/screenshots/03-access-inspection.png) | ![Radio report form: emergency dispatch panel](docs/screenshots/04-radio-report.png) | | ![Access inspection: wristband/credential check with allow/deny prompt](docs/screenshots/03-access-inspection.png) | ![Radio report form: emergency dispatch panel](docs/screenshots/04-radio-report.png) |
| ![Late night: street lamps lit, marquee glowing, dark Hollywood Boulevard](docs/screenshots/05-late-night-avenue.png) | ![Night shift: guard flashlight cutting through the dark](docs/screenshots/06-night-flashlight.png) | | ![Late night: street lamps lit, marquee glowing, dark Hollywood Boulevard](docs/screenshots/05-late-night-avenue.png) | ![Night shift: guard flashlight cutting through the dark](docs/screenshots/06-night-flashlight.png) |
| ![Wand screening at The Joke Shop: false alarm on keys](docs/screenshots/07-wand-screening.png) | ![Escalation HUD at L3 with body camera armed and reaction log](docs/screenshots/08-escalation-hud.png) |
| ![Body-camera timeline: formal warning, body camera on, blocking](docs/screenshots/09-body-camera-timeline.png) | |
*Screenshots are generated by [`src/test/capture_screenshots.gd`](src/test/capture_screenshots.gd) from the current build — day and late-night shifts.* *Screenshots are generated by [`src/test/capture_screenshots.gd`](src/test/capture_screenshots.gd) from the current build — day, late-night, wand screening, escalation, and shift-review timeline.*
## ⬇️ Downloads ## ⬇️ Downloads
@@ -32,7 +34,7 @@ The central design goal is simple:
> Downloads are public (no sign-in required) since the host's instance-wide > Downloads are public (no sign-in required) since the host's instance-wide
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31. > `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
> >
> **Latest: v0.3.1** — props draw with a single atlas material instead of one material per part. > **Latest: v0.3.3** — staged captures for the wand panel, escalation HUD, and body-camera timeline now feed the visual checks.
> 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
@@ -258,10 +260,12 @@ 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 19 screens: the six README captures plus the start menu, The catalogue covers 19 screens: the nine README captures plus the start menu,
pause overlay, settings, shift review, radial menu, emergency radio, wand pause overlay, settings, shift review, radial menu, emergency radio, wand
screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival, screening, Joke Shop queue, TV Studio arrival, crowd panic, responder arrival,
body-camera timeline, and the escalation HUD. body-camera timeline, and the escalation HUD. All 19 have an AI reference in
`docs/screens/designs/`; the nine screens with 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
+1 -3
View File
@@ -1,6 +1,6 @@
# TODO # TODO
Godot 4.3 prototype. Latest release **v0.3.1** (prop atlas). Headless suite **182/182** plus Godot 4.3 prototype. Latest release **v0.3.2** (mockup set). Headless suite **182/182** plus
**53** Python tooling tests; the session, PLAN-verb, crowd, emergency, timeline, audio, scenario-content, and **53** Python tooling tests; the session, PLAN-verb, crowd, emergency, timeline, audio, scenario-content, and
windowed focus/radial smokes pass. Anonymous downloads are enabled on the host. windowed focus/radial smokes pass. Anonymous downloads are enabled on the host.
@@ -14,5 +14,3 @@ All planned backlog sections are complete (see `DONE.md`). Optional future ideas
- [ ] Real voice/dialogue audio (the crowd bed and radio crackle are procedural). - [ ] Real voice/dialogue audio (the crowd bed and radio crackle are procedural).
- [ ] Swap the primitive prop builder for an image-to-mesh model API behind the same - [ ] Swap the primitive prop builder for an image-to-mesh model API behind the same
`assets/models/generated/<prop>.glb` contract. `assets/models/generated/<prop>.glb` contract.
- [ ] Regenerate the AI screen mockups for the seven new catalogue entries (only the
deterministic wireframes exist so far).
Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

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.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 KiB

After

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

+1 -1
View File
@@ -8,7 +8,7 @@ config_version=5
config/name="Step Line" config/name="Step 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.3.1" config/version="0.3.3"
run/main_scene="res://scenes/main.tscn" run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.3", "GL Compatibility") config/features=PackedStringArray("4.3", "GL Compatibility")
+6
View File
@@ -402,6 +402,12 @@ func set_escalation(level: int) -> void:
if vignette_rect: if vignette_rect:
vignette_rect.modulate = Color(tint.r, tint.g, tint.b, 0.05 + 0.2 * float(level) / 5.0) vignette_rect.modulate = Color(tint.r, tint.g, tint.b, 0.05 + 0.2 * float(level) / 5.0)
## Dev helper: drop queued log lines so a staged screenshot shows only the
## lines that belong to the moment.
func clear_log() -> void:
_log_lines.clear()
log_label.text = ""
func log(text: String) -> void: func log(text: String) -> void:
_log_lines.append(text) _log_lines.append(text)
if _log_lines.size() > 8: if _log_lines.size() > 8:
+94 -1
View File
@@ -13,6 +13,7 @@ var state := 0
var state_frames := 0 var state_frames := 0
var targeted := false var targeted := false
var camera_staged := false var camera_staged := false
var wand_attempts := 0
func _initialize() -> void: func _initialize() -> void:
root.size = Vector2i(1280, 720) root.size = Vector2i(1280, 720)
@@ -89,7 +90,7 @@ func _advance() -> void:
if gm != null: if gm != null:
gm.radio_panel.hide() gm.radio_panel.hide()
gm.hud.set_action_hint("") gm.hud.set_action_hint("")
gm.coordinator.elapsed_s = 750.0 gm.coordinator.elapsed_s = 620.0
state = 5 state = 5
state_frames = 0 state_frames = 0
5: 5:
@@ -110,9 +111,101 @@ func _advance() -> void:
gm.call("_toggle_flashlight") gm.call("_toggle_flashlight")
if state_frames >= 30: if state_frames >= 30:
_shoot("06-night-flashlight.png") _shoot("06-night-flashlight.png")
# Move to The Joke Shop for the wand/shift-review set, and keep
# the late-night phase so the UI sits on the night scene.
if gm != null:
gm.call("_toggle_flashlight")
gm.switch_scenario("joke_shop")
gm.coordinator.elapsed_s = 620.0
state = 7 state = 7
state_frames = 0
7:
# Wand screening: wait for guests and keep re-targeting until one
# accepts the screen (some authored Joke Shop guests refuse it).
if state_frames > 40 and state_frames % 20 == 0 and gm != null and gm.npcs.size() > 0:
# Prefer a guest whose items make the wand alert, so the panel
# shows the interesting case instead of "Alert: none".
if gm.wand_open:
if _wand_alerts():
state = 8
state_frames = 0
elif wand_attempts >= 6:
# Nobody nearby carried an alerting item: stage the documented
# false-alarm case (keys) on this guest and re-open the panel.
var guest: Object = gm.call("_targeted_controller")
if guest != null and guest.profile != null:
var false_alarm: Array[String] = ["keys"]
guest.profile.carried_items = false_alarm
gm.call("_toggle_wand_screening")
gm.call("_toggle_wand_screening")
state = 8
state_frames = 0
else:
gm.call("_toggle_wand_screening")
wand_attempts += 1
else:
gm.call("_cycle_nearby_target")
_face_target()
gm.call("_toggle_wand_screening")
wand_attempts += 1
elif state_frames > 600 and gm != null:
# Fallback: real controller, panel shown directly.
var controller: Object = gm.call("_targeted_controller")
if controller != null and controller.profile != null:
gm.hud.show_wand(controller, gm.wand_screener.screen(controller.profile.carried_items))
state = 8
state_frames = 0
8:
if state_frames == 1 and gm != null:
gm.hud.clear_log()
_stage_closeup()
if state_frames >= 45:
_shoot("07-wand-screening.png")
if gm != null:
if gm.wand_open:
gm.call("_toggle_wand_screening")
# Real escalation path: formal warning arms the body camera
# (a timeline row), then blocking raises the meter to L3.
gm.call("_apply_interaction", 2, "formal warning")
gm.call("_apply_interaction", 3, "blocking")
state = 9
state_frames = 0
9:
if state_frames == 1 and gm != null:
# Jump into the late phase now that guests exist: LATE has no
# routine spawn pool, so this happens after staging the target.
gm.coordinator.elapsed_s = 760.0
_stage_closeup()
if state_frames >= 45:
_shoot("08-escalation-hud.png")
if gm != null and gm.coordinator != null:
# Close the open incident so the recorder's review includes it
# (to_dict only lists closed incidents).
gm.coordinator.recorder.close_all_open("unresolved")
gm.call("_open_timeline", {"incidents": gm.coordinator.recorder.to_dict()})
state = 10
state_frames = 0
10:
if state_frames >= 30:
_shoot("09-body-camera-timeline.png")
quit(0) quit(0)
## Put the guard a couple of steps in front of the target so a staged UI capture
## frames the person the panel is about.
func _wand_alerts() -> bool:
if gm == null or gm.hud == null or gm.hud.wand_text == null:
return false
return gm.hud.wand_text.text.contains("Alert: YES")
func _stage_closeup() -> void:
var target: Node3D = gm.targeted_npc
if target == null or not is_instance_valid(target):
return
gm.guard.global_position = target.global_position + Vector3(0, -0.4, 2.2)
gm.guard.global_position.y = 0.1
_face_target()
gm.guard.head.rotation.x = deg_to_rad(1.0)
func _face_target() -> void: func _face_target() -> void:
var target: Node3D = gm.targeted_npc var target: Node3D = gm.targeted_npc
if target == null: if target == null:
+16 -12
View File
@@ -342,7 +342,7 @@ SCREENS = [
"kind": "ui", "kind": "ui",
"title": "Wand screening", "title": "Wand screening",
"source": "README screenshot gallery · PLAN The Joke Shop · src/core/wand_screener.gd", "source": "README screenshot gallery · PLAN The Joke Shop · src/core/wand_screener.gd",
"ref": None, "ref": "07-wand-screening.png",
"description": "Handheld wand screening panel for The Joke Shop: the alert " "description": "Handheld wand screening panel for The Joke Shop: the alert "
"result, the items the guest is carrying, and allow/deny " "result, the items the guest is carrying, and allow/deny "
"buttons. A phone, keys or a belt buckle can raise a false " "buttons. A phone, keys or a belt buckle can raise a false "
@@ -452,7 +452,7 @@ SCREENS = [
"kind": "ui", "kind": "ui",
"title": "Body-camera timeline", "title": "Body-camera timeline",
"source": "README screenshot gallery · ADR-0005 · src/game/ui/event_timeline.gd", "source": "README screenshot gallery · ADR-0005 · src/game/ui/event_timeline.gd",
"ref": None, "ref": "09-body-camera-timeline.png",
"description": "The shift review's body-camera timeline: a text list of every " "description": "The shift review's body-camera timeline: a text list of every "
"recorded incident event stamped mm:ss with the person and the " "recorded incident event stamped mm:ss with the person and the "
"action or outcome, one row highlighted, and previous/next " "action or outcome, one row highlighted, and previous/next "
@@ -475,22 +475,25 @@ SCREENS = [
"kind": "ui", "kind": "ui",
"title": "Escalation HUD", "title": "Escalation HUD",
"source": "README accessibility note · Hud.escalation_meter (shape meter · level)", "source": "README accessibility note · Hud.escalation_meter (shape meter · level)",
"ref": None, "ref": "08-escalation-hud.png",
"description": "The in-shift HUD: a shape meter plus numeric escalation level " "description": "The in-shift HUD: a shape meter plus numeric escalation level "
"(`Escalation: Blocking [###--] L3/5`), the targeted person's " "(`Escalation: Blocking [###--] L3/5`), the targeted person's "
"name, flashlight / door / body-camera indicators, shift clock, " "name, flashlight / door / body-camera indicators, shift clock, "
"and the reaction log.", "and the reaction log.",
"prompt": ( "prompt": (
"Realistic 3D game screenshot with a minimal in-game HUD overlaid: an " "Third-person over-the-shoulder 3D game screenshot at night: a security "
"escalation meter drawn as a shape bar with the text Blocking and L3/5, " "guard in the foreground facing a guest in front of a venue entrance, "
"a target name at the bottom centre, small indicator icons for flashlight, " "with a minimal in-game HUD overlaid. Bottom left: an escalation meter "
"door lock and body camera, a shift clock at the top right and a short " "drawn as a shape bar reading Escalation: Blocking [###--] L3/5. Centre: "
"reaction log at the left, over a softly blurred daytime street"), "a small target name label above the guest. Top right: a shift clock, "
"reputation and crowd percentage. Left: a short stacked reaction log. "
"Bottom centre: a control hint bar. Realistic lighting, muted colours"),
"regions": [ "regions": [
(0.30, 0.02, 0.70, 0.10, "escalation shape meter"), (0.02, 0.80, 0.34, 0.95, "escalation shape meter"),
(0.30, 0.86, 0.70, 0.96, "target name + hint"), (0.35, 0.28, 0.75, 0.40, "target name label"),
(0.78, 0.02, 0.98, 0.14, "shift clock"), (0.78, 0.02, 0.98, 0.16, "shift clock + crowd"),
(0.02, 0.60, 0.26, 0.95, "reaction log"), (0.02, 0.20, 0.36, 0.65, "reaction log"),
(0.02, 0.94, 0.98, 0.99, "control hints"),
], ],
}, },
] ]
@@ -592,6 +595,7 @@ def _generate_ai(screen, model, request_size):
def generate(args): def generate(args):
_load_env_file()
DESIGNS_DIR.mkdir(parents=True, exist_ok=True) DESIGNS_DIR.mkdir(parents=True, exist_ok=True)
failed = [] failed = []
selected = [screen for screen in SCREENS selected = [screen for screen in SCREENS