diff --git a/MEMORY.md b/MEMORY.md index 7af2bc7..85e99da 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -25,7 +25,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o - Newly added: LATE shift phase (start 700s) with a reachability test; PROTESTER archetype + events + colors; CALL_SOMEONE reaction; `DoorState` core class + `L` binding + visual indicator; flashlight (`G`, SpotLight3D, perception bonus at evening/late); persistence wired (`Save.save_shift_result` on shift end, history + best score on the review screen); `NavigationRegion3D` flat navmesh + `NavigationAgent3D` NPC paths (steps now visual-only); strings moved to `res://content/strings.en.json` (41 keys, loader keeps `lookup`/`fmt`); `export_presets.cfg` (Linux/Windows/macOS) + `tools/build_releases.sh` + `docs/RELEASING.md`; README Downloads section. - Run tests: `godot --headless --path . --script res://src/test/test_runner.gd` - Boot check: `godot --headless --path . --quit-after 120` -- Screenshot tool: `godot --path . --script res://src/test/take_screenshot.gd` (writes to `user://screenshot.png`). +- Screenshot tools: `godot --path . -s res://src/test/take_screenshot.gd` (single shot to `user://screenshot.png`); `godot --path . -s res://src/test/capture_screenshots.gd` (staged README gallery shots to `user://screenshots/`, copied to `docs/screenshots/`). - Release build verified locally: export templates for 4.3 installed at `~/.local/share/godot/export_templates/4.3.stable/`; `./tools/build_releases.sh` produced Linux/Windows/macOS artifacts for 0.1.0 in `build/` and the Linux binary passed a headless smoke test. Publishing to Gitea still needs a token (see `tools/publish_release.sh`, `docs/RELEASING.md`). ## Known Issues / Notes diff --git a/README.md b/README.md index 1ad9554..b9f7513 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,15 @@ The central design goal is simple: > The player wins by choosing the lowest level of escalation that keeps everyone safe. +## Screenshots + +| | | +|---|---| +| ![Venue overview: the Hollywood Boulevard entrance with steps, sidewalk, and restricted door](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) | + +*Screenshots are generated by [`src/test/capture_screenshots.gd`](src/test/capture_screenshots.gd) from the current build.* + ## ⬇️ Downloads | Platform | Package | diff --git a/docs/screenshots/01-venue-overview.png b/docs/screenshots/01-venue-overview.png new file mode 100644 index 0000000..da215ae Binary files /dev/null and b/docs/screenshots/01-venue-overview.png differ diff --git a/docs/screenshots/02-targeting-npc.png b/docs/screenshots/02-targeting-npc.png new file mode 100644 index 0000000..49f211e Binary files /dev/null and b/docs/screenshots/02-targeting-npc.png differ diff --git a/docs/screenshots/03-access-inspection.png b/docs/screenshots/03-access-inspection.png new file mode 100644 index 0000000..3355d80 Binary files /dev/null and b/docs/screenshots/03-access-inspection.png differ diff --git a/docs/screenshots/04-radio-report.png b/docs/screenshots/04-radio-report.png new file mode 100644 index 0000000..08f33dc Binary files /dev/null and b/docs/screenshots/04-radio-report.png differ diff --git a/src/test/capture_screenshots.gd b/src/test/capture_screenshots.gd new file mode 100644 index 0000000..309b5a7 --- /dev/null +++ b/src/test/capture_screenshots.gd @@ -0,0 +1,85 @@ +extends SceneTree +## Dev helper: run the game windowed and capture staged screenshots for the +## README. Saves 1600x900 PNGs to user://screenshots/. +## Usage: godot --path . --script res://src/test/capture_screenshots.gd + +const OUT_DIR := "user://screenshots" +const MAX_FRAMES := 2400 # hard cap (~40s) + +var frames := 0 +var gm: Node = null +var state := 0 +var state_frames := 0 +var targeted := false + +func _initialize() -> void: + root.size = Vector2i(1600, 900) + var packed: PackedScene = load("res://scenes/main.tscn") + var inst: Node = packed.instantiate() + root.add_child(inst) + +func _process(_delta: float) -> bool: + frames += 1 + # The main scene's _ready may not have run when _initialize added it; + # look for GameManager lazily each frame until it exists. + if gm == null: + gm = root.find_child("GameManager", true, false) + if gm == null and frames > 120: + push_error("GameManager not found") + quit(1) + return true + if frames > MAX_FRAMES: + print("timeout reached") + quit(0) + return true + _advance() + return false + +func _advance() -> void: + state_frames += 1 + match state: + 0: + # 2s overview from the guard post. + if state_frames >= 120: + _shoot("01-venue-overview.png") + state = 1 + state_frames = 0 + 1: + # Wait for a nearby NPC so the interaction HUD has a target. + if gm != null and gm.npcs.size() > 0: + gm.call("_cycle_nearby_target") + targeted = true + state = 2 + state_frames = 0 + 2: + # Give the HUD a moment, then shoot the interaction shot. + if state_frames >= 30: + _shoot("02-targeting-npc.png") + if targeted and gm != null: + gm.call("_toggle_inspection") + state = 3 + state_frames = 0 + 3: + if state_frames >= 30: + _shoot("03-access-inspection.png") + if gm != null: + gm.hud.hide_inspection() + # Re-target (target may have moved) before opening the radio form. + if gm.npcs.size() > 0: + gm.call("_cycle_nearby_target") + gm.call("_open_radio", false) + state = 4 + state_frames = 0 + 4: + if state_frames >= 30: + _shoot("04-radio-report.png") + state = 5 + quit(0) + +func _shoot(file_name: String) -> void: + var dir := DirAccess.open("user://") + if dir == null or not dir.dir_exists("screenshots"): + dir.make_dir_recursive("screenshots") + 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()])