Compare commits

...
3 Commits
Author SHA1 Message Date
zwitschi f7486d7539 Add README screenshots: staged in-game captures between description and Downloads
CI / headless-tests (push) Successful in 2m22s
- New capture tool src/test/capture_screenshots.gd (windowed, staged:
  venue overview, targeting an NPC, access inspection, radio report)
- Generated shots saved to docs/screenshots/ and embedded as a 2x2 gallery
  in README above the Downloads section
2026-10-31 13:01:40 +01:00
zwitschi eb1776e614 Update architecture docs and README with versioning, CI/CD, and release management details 2026-10-31 13:01:29 +01:00
zwitschi 191377fae2 Prepare first downloadable release: install export templates, build 0.1.0 binaries, add publish helper
- Install Godot 4.3 export templates locally and fix macOS export
  (enable rendering/textures/vram_compression/import_etc2_astc)
- Build Linux/Windows/macOS artifacts into build/ via tools/build_releases.sh;
  Linux binary passes headless smoke test
- Add tools/publish_release.sh (creates Gitea release + uploads assets with a token)
- TODO: mark build/install steps done; publishing v0.1.0 + link verification
  remain as the single action blocker (needs GITEA_TOKEN)
2026-10-31 12:57:20 +01:00
19 changed files with 213 additions and 6 deletions

No files matched your search

+3 -3
View File
@@ -25,8 +25,8 @@ 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`).
- Release build: `./tools/build_releases.sh` requires export templates (see `docs/RELEASING.md`).
- 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
@@ -34,7 +34,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- Headless (dummy renderer) logs "Parameter m is null" from `mesh_get_surface_count`; it does not occur with a real GL driver and does not affect logic.
- Steps are visual-only (no collider) so NPCs can path to the entrance; the guard spawns on the landing so it never needs to climb.
- Persistence tests write into the real `user://shift_results.json` (harmless local pollution of the demo history).
- Export presets and release scripts are **not yet verified with real export templates** (none installed on this machine); see `docs/RELEASING.md` before first release.
- macOS export required `rendering/textures/vram_compression/import_etc2_astc=true` in `project.godot` (universal build demands ETC2 ASTC enabled).
- Joke Shop `door_staff_lines` authored but not yet surfaced in the prototype UI (tracked in TODO).
## Open Questions
+10 -1
View File
@@ -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 |
@@ -168,7 +177,7 @@ The unit test runner is the project's own lightweight harness (no editor plugin
## Architecture
The full arc42-style architecture documentation lives in [`docs/architecture.md`](docs/architecture.md). It covers goals, constraints, context, solution strategy, building blocks, runtime views, deployment, cross-cutting concepts, quality requirements, risks, and a glossary.
The full arc42-style architecture documentation lives in [`docs/architecture.md`](docs/architecture.md). It covers goals, constraints, context, solution strategy, building blocks, runtime views, deployment, cross-cutting concepts, quality requirements, risks, a glossary, the CI/CD build pipeline, semantic versioning, and release management.
## Project Documentation
+16 -1
View File
@@ -1,11 +1,26 @@
# TODO
## 🔴 PRIORITY — Ship downloadable builds (so the README Downloads links work)
Goal: users hitting the README **Downloads** section can actually download a playable version from
`https://git.allucanget.biz/zwitschi/security-game/releases`.
- [x] Install Godot 4.3 export templates locally (see `docs/RELEASING.md` step 1).
- [x] Run `./tools/build_releases.sh` and confirm the three artifacts appear in `build/`.
- [x] Smoke-test the Linux binary launches (`./build/step-line-linux-0.1.0.x86_64 --headless --quit-after 120` → 0 script errors).
- [ ] Create a Gitea release `v0.1.0` (tag matching `config/version` in `project.godot`) — **needs `GITEA_TOKEN`**: `GITEA_TOKEN=... ./tools/publish_release.sh 0.1.0`.
- [ ] Verify the README Downloads links resolve: visit the release page and download one artifact.
- [x] (Local workflow) `tools/publish_release.sh` exists so future releases need only a `GITEA_TOKEN`.
### Follow-ups once the first release is live
- [ ] CI packaging: build and upload artifacts automatically on tags.
## Future Systems (design-level backlog)
- [ ] Body-camera replay timeline in the shift review.
- [ ] Long (10-15 minute) shift lengths and phase-based lighting (darkened evening / late phases).
- [ ] Venue selection screen (wire `switch_scenario` to a startup menu).
- [ ] CI packaging: upload `build/` artifacts to releases automatically on tags.
- [ ] IDE/editor-based GUT integration when a Godot editor workflow is introduced.
- [ ] Reduce RefCounted reference cycles (NPCController <-> FSM) noted in `MEMORY.md`.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+12
View File
@@ -54,6 +54,18 @@ Artifacts are written to `build/`:
The repository lives at `https://git.allucanget.biz/zwitschi/security-game`.
Preferred path — use the helper script with a token:
```bash
GITEA_TOKEN=your_api_token ./tools/publish_release.sh 0.1.0
```
The script creates the release from `project.godot`'s `config/version`, uploads
all three `build/` artifacts, and prints the release URL. The token needs only
`write:repository` scope on the Gitea host.
Manual alternative:
### Via the web UI
1. Create a **New Release** with tag `v<version>`.
+28
View File
@@ -28,6 +28,15 @@ This document follows the structure of the arc42 architecture documentation temp
- Venue-specific policies must be configurable rather than hard-coded into individual NPCs.
- The game must preserve the de-escalation-first design described in [`PLAN.md`](../PLAN.md).
- The project uses the repository workflow described in [`AGENTS.md`](../AGENTS.md).
- The project follows [Semantic Versioning](https://semver.org/) (`MAJOR.MINOR.PATCH`).
### Version management
- **MAJOR** — incompatible changes to the game's core systems, save format, or public-facing behavior.
- **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible.
- **PATCH** — bug fixes and small polish that do not change behavior.
The single source of truth for the version is `config/version` in `project.godot` (currently `0.1.0`). Each release is tagged in git as `v<version>` (e.g. `v0.1.0`). The `0.x` series is pre-1.0, 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
@@ -103,6 +112,25 @@ The initial target is a single-player local game. The expected runtime consists
Future options include external analytics, cloud saves, or replay sharing, but they are not part of the initial architecture.
### CI/CD build pipeline
A continuous-integration pipeline keeps the headless test suite green on every change, with a planned extension to build and publish release artifacts automatically.
- **CI workflow** (`.github/workflows/ci.yml`) runs on every push to `main` and on every pull request. It installs the pinned Godot 4.3 editor, caches the `.godot` import artifacts, imports the project to build the class cache, and runs the headless test suite (`res://src/test/test_runner.gd`). The job fails on a non-zero exit code or any `SCRIPT ERROR` in the output.
- **Planned extension** grows the pipeline from "test only" to "test, build, and publish": a test stage (headless tests plus a `--quit-after 120` boot smoke test as the merge gate), a build stage (install export templates, run `tools/build_releases.sh`), and a publish stage (on a version tag, upload artifacts via `tools/publish_release.sh` with a `GITEA_TOKEN` secret).
- **Hosting note:** the repository lives on Gitea/Forgejo (`https://git.allucanget.biz/zwitschi/security-game`); the workflow is authored for GitHub Actions and should be mirrored or ported to the host's own runner if Actions is unavailable there. Export templates must match the pinned engine version, and macOS builds are unsigned (production releases need notarization or clear quarantine guidance).
### Release management
Releases are produced from a tagged commit and published to the repository host's release page, where the README "Downloads" links point.
- `export_presets.cfg` — committed Linux, Windows Desktop, and macOS export presets so builds are reproducible.
- `tools/build_releases.sh` — exports all three desktop artifacts into `build/`, named `step-line-<platform>-<version>.<ext>`.
- `tools/publish_release.sh` — creates a release and uploads the artifacts via the Gitea/Forgejo API (requires `GITEA_TOKEN`).
- [`RELEASING.md`](RELEASING.md) — step-by-step guide and release checklist.
The release process is: bump the version, run the full test suite and boot smoke test, run `tools/build_releases.sh`, run `tools/publish_release.sh` (or create the release via the web UI), and verify the README "Downloads" links resolve to the new release.
## 8. Cross-Cutting Concepts
- **Event-based incident recording:** Important actions emit records consumed by evidence, reputation, and review systems.
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

+1 -1
View File
@@ -92,7 +92,7 @@ custom_template/release=""
debug/export_console_wrapper=0
binary_format/embed_pck=false
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
texture_format/etc2_astc=true
codesign/enable=false
codesign/timestamp=true
codesign/identity_type=0
+1
View File
@@ -105,3 +105,4 @@ print_debug={
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
textures/vram_compression/import_etc2_astc=true
+85
View File
@@ -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()])
Regular → Executable
View File
File mode changed.
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Publish a release to the Gitea/Forgejo host, uploading the built artifacts.
#
# Requires:
# - GITEA_TOKEN (a token with write access to the repo)
# - build artifacts from tools/build_releases.sh (or already exported)
#
# Usage:
# GITEA_TOKEN=xxx ./tools/publish_release.sh [version]
# (version defaults to config/version from project.godot)
set -euo pipefail
REPO="${REPO:-zwitschi/security-game}"
HOST="${HOST:-https://git.allucanget.biz}"
API="$HOST/api/v1/repos/$REPO/releases"
VERSION="${1:-$(grep -oP 'config/version="\K[^"]+' project.godot)}"
TAG="v$VERSION"
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "error: GITEA_TOKEN is not set (see docs/RELEASING.md)" >&2
exit 1
fi
# Artifacts must already exist (run tools/build_releases.sh first).
printf -v BS 'build/step-line-linux-%s.x86_64' "$VERSION"
printf -v BS_WIN 'build/step-line-windows-%s.exe' "$VERSION"
printf -v BS_MAC 'build/step-line-macos-%s.zip' "$VERSION"
for f in "$BS" "$BS_WIN" "$BS_MAC"; do
if [[ ! -f "$f" ]]; then
echo "error: missing artifact $f — run ./tools/build_releases.sh first" >&2
exit 1
fi
done
echo "==> Creating release $TAG on $HOST/$REPO"
RELEASE_JSON="$(curl -s -X POST "$API" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Step Line $VERSION — playable prototype. See README for controls.\",\"draft\":false,\"prerelease\":false}")"
RELEASE_ID="$(echo "$RELEASE_JSON" | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)"
if [[ -z "$RELEASE_ID" ]]; then
echo "error: could not create release; response:" >&2
echo "$RELEASE_JSON" >&2
exit 1
fi
echo "Release id: $RELEASE_ID"
for f in "$BS" "$BS_WIN" "$BS_MAC"; do
echo "==> Uploading $(basename "$f")"
curl -s -X POST "$API/$RELEASE_ID/assets" \
-H "Authorization: token $GITEA_TOKEN" \
-F "attachment=@$f" >/dev/null
done
echo ""
echo "Release live: $HOST/$REPO/releases/tag/$TAG"