Capture the wand panel, escalation HUD and body-camera timeline
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.
No files matched your search
@@ -691,3 +691,21 @@ tooling **53/53**.
|
|||||||
`OPENROUTER_API_KEY` by hand.
|
`OPENROUTER_API_KEY` by hand.
|
||||||
- [x] Verified every reference is 1280x720, reviewed the new images visually, and confirmed
|
- [x] Verified every reference is 1280x720, reviewed the new images visually, and confirmed
|
||||||
`list` reports 19 designs (12 kept, 7 new).
|
`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.
|
||||||
@@ -41,6 +41,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
catalogue screens. Only the six staged captures (01–06) are compared/judged; the other 13
|
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
|
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()`.
|
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
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ The central design goal is simple:
|
|||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|  | |
|
||||||
|
|
||||||
*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.2** — every screen in the design catalogue now has an AI reference mockup.
|
> **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,11 +260,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 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. All 19 have an AI reference in
|
body-camera timeline, and the escalation HUD. All 19 have an AI reference in
|
||||||
`docs/screens/designs/`; the six screens with staged captures are also compared
|
`docs/screens/designs/`; the nine screens with staged captures are also compared
|
||||||
or judged against the running game.
|
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
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 838 KiB After Width: | Height: | Size: 836 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 528 KiB |
@@ -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.2"
|
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")
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||