Compare commits

...
7 Commits
Author SHA1 Message Date
zwitschi b8507df346 Harden release publishing and document release blockers
Full build / Import + headless tests (push) Successful in 45s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 07:32:21 +01:00
zwitschi 60bf9a8ef6 Add save migration fixtures and failure-mode coverage 2026-11-03 07:29:42 +01:00
zwitschi b51bdc8efe Open the 0.10.0 release-candidate milestone
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 06:20:36 +01:00
zwitschi a7b2852253 Record the v0.9.6 release build
Full build / Import + headless tests (push) Successful in 39s
Full build / Full desktop build + optional release (push) Skipped
2026-11-03 06:19:38 +01:00
zwitschi 4bb2896819 Verify the 0.9.6 integration checkpoint
Full build / Import + headless tests (push) Successful in 53s
Full build / Full desktop build + optional release (push) Successful in 4m27s
2026-11-03 06:13:07 +01:00
zwitschi f5b8ef2e70 Prepare the 0.9.6 integration checkpoint 2026-11-03 06:11:17 +01:00
zwitschi 0044f5f065 Record the v0.9.5 release build
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
2026-11-02 21:12:00 +01:00
37 changed files with 979 additions and 161 deletions

No files matched your search

+9
View File
@@ -4,6 +4,15 @@ All notable user-facing changes to Gig Line. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use
Semantic Versioning.
## [0.9.6] — 2026-11-02
### Changed
- The UI, model and texture passes are now integrated into a verified checkpoint
build. The gallery, NPC portraits, realism captures, asset audits, smoke
matrix, 24-NPC budget and Web/Desktop HQ packages were regenerated and
re-checked; no gameplay or content changes were introduced.
## [0.9.5] — 2026-11-02
### Changed
+64
View File
@@ -0,0 +1,64 @@
# Credits and Provenance
Gig Line is a local, single-player security-shift game built with Godot 4 and
GDScript. This file records the asset provenance needed for the release review;
it is not a legal opinion or a claim that every provider licence has already
been cleared for public distribution.
## Game, code and design
- Game design, systems, scenarios, dialogue and UI code: the Gig Line project.
- Engine: [Godot Engine 4.7.2](https://godotengine.org/), MIT licence.
- The game bundles no third-party fonts; it uses Godot's default theme font.
- No third-party music, sound effects or voice recordings are bundled. Crowd
ambience and radio cues are generated procedurally at runtime.
## 3D characters, gear and props
The character, gear and prop library is generated by project-owned scripts
(`tools/blender_assets.py`, `tools/npc_pipeline.py`, `tools/model_pipeline.py`)
using Blender. The `.blend` working sources are developer artifacts and are not
shipped. The exported `.glb` files and baked AO maps are project assets; they do
not embed third-party models or purchased asset packs.
The venue geometry is authored procedurally in `src/game/world_builder.gd` as a
stylized reconstruction. The Joke Shop is a fictional stand-in for the real
Sunset Strip block; the game does not claim endorsement by, or an exact replica
of, any real venue or tenant.
## AI-assisted images
Surface textures, skyline panoramas, UI art and concept references were produced
with image-generation calls from the project's tooling. The default model slugs
recorded in the source are:
- `bytedance-seed/seedream-4.5` — `tools/generate_textures.py`
- `openai/gpt-5-image-mini` — `tools/asset_pipeline.py`,
`tools/model_pipeline.py`, `tools/scene_concepts.py` and
`tools/screen_designs.py`
- Judging/review calls use the model selected by `tools/openrouter_judge.py` and
are development-only; they do not ship in the game.
The prompts and generation workflow live in those tools. Per-file generation
metadata is not stored with every image, so the exact provider, model version and
generation date of each committed bitmap cannot be reconstructed from the
repository alone. Before a public 0.10.0 release, the provider terms and
commercial-use/redistribution rights for those generated outputs must be
reviewed and recorded. **This is a release blocker, not an assumed clearance.**
Reference photographs linked from the venue documents are hosted on Wikimedia
Commons and were used for proportion/material study only. They are not included
in the game or its release packages. If any future derivative asset copies a
reference more directly, its attribution and licence must be added here.
## Audio
All runtime audio is procedural (crowd bed, radio crackle and interface cues).
There are no external audio files, so there is no separate music/SFX licence to
clear. See `docs/accessibility.md` for caption and volume behaviour.
## Third-party names
The game is a work of fiction. Real place names and public references are used
for orientation and satire/stand-in geography; no real person, venue or brand
endorses the game. Review the storefront and trademark language before release.
+154
View File
@@ -1879,3 +1879,157 @@ unexplained regressions. Accepted limits: hand items use the forward-offset open
a finger-curl rig; fabric folds stay out of the low-poly target; the >26 m impostor remains a
two-quad billboard; evening readability is represented by the day/night/LATE final-context set.
Tagged `v0.9.4`; Gitea run 916 passed checks and the full build and published seven assets.
## Patch 0.9.5 — Texture and material refinement
Goal: improve close-range surface and character texture quality while keeping GL
Compatibility/Web parity, deterministic assets and offline CI.
### Environment textures and materials
- [x] Audit the v2 picks (brick, sidewalk pavers, dry/wet asphalt, awning canvas,
day/dusk/night skylines) at close range and in the entrance/street shots for
seams, tiling, scale, contrast and normal-map strength. (New `surfaces` and
`surfaces_joke` capture stages; no seam or UV-scale failures found.)
- [x] Refine brick mortar/wear, paver joints/stains, asphalt aggregate/wet response
and awning weave using the existing image/asset tooling; never overwrite a
pick, use `-vN` candidates and promote validated results. (The Walk of Fame
band now uses the paver/terrazzo texture; the neighbour awning keeps its
cream stripe via a neutral tint and 4× UV. Brick/asphalt picks held up.)
- [x] Check UV scales and material roughness/metallic profiles; keep day/night
phase behavior and the wet-street transition intact. (Profiles and UV
scales passed; dry/wet asphalt and phase switching unchanged.)
- [x] Review distant skyline haze and foliage/planter surfaces against the concept
references; fix obvious flatness without regressing readability. (Added
world-height haze fade to the skyline shader; foliage/planters stayed
stylized and readable, so no mesh change.)
### Character and prop textures
- [x] Audit NPC skin, cloth, pants, hair and gear albedo/normal/AO maps in portraits;
refine fabric weave, skin variation and fixed detail materials. (256² maps rebuilt; fabric
now uses a 4× UV weave and skips baked per-character AO, removing the portrait blotches;
skin/hair/gear keep AO. Comparison `sheet_npc_baseline_compare-texuv.jpg`.)
- [x] Re-bake AO where geometry changes; verify the 256² maps and material wiring.
(No character geometry changed in the texture pass; asset/base audits pass.)
- [x] Audit library props and UI/card/icon textures at their actual screen sizes;
remove blurry/oversized or unused textures where safe. (All 21 screen-art
textures are 1280×720 and referenced; access cards are 256×128, HUD icons
64×64; no oversized or unused runtime texture found. Legacy root textures
remain only as the `generate_textures.py`/asset-test fallback.)
- [x] Run texture audits, import, and the full asset test suite; record texture memory
and triangle/draw-call impact. (Godot 263/263, Python 101/101; asset and base
audits pass; 24 NPCs = 168 meshes / 131,616 triangles / 6.94 ms. Compressed
payloads: picks 24.55 MB, UI art 22.67 MB, NPC maps 0.48 MB.)
### Visual verification
- [x] Run `python3 tools/scene_concepts.py realism` for the affected stages with the
judge enabled; record metric gaps and model/spend. (Run
`docs/screens/realism/2026-11-02-tex` with `z-ai/glm-5.3-flash`, one pass per
shot; mean `after` overall 1.8, lighting 2.3, materials 1.8, texture 1.2,
backdrop 2.0, characters 1.2, Pillow gap 0.306. The judge is optional and not
a CI gate; the report stays local.)
- [x] Re-run `tools/check_screenshots.sh` and the screen-design judge; clear all
`diff` verdicts. (21 good / 0 bad; `screen_designs.py judge` = 21/21 match,
0 review, 0 diff.)
- [x] Capture before/after close-ups for brick, pavement, asphalt, character cloth
and skyline; document accepted trade-offs. (Committed
`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`: Walk of Fame
pavers, asphalt aggregate, limestone facade, day skyline haze and NPC cloth;
asphalt/limestone picks intentionally held, no replacement.)
- [x] Update asset docs and the changelog/status with the promoted texture versions.
(README/PLAN/architecture/status/MEMORY and the 0.9.5 changelog entry now
carry the texture results; the 0.9.5 section moved to `DONE.md` after the
release build.)
Completed 2026-11-02: all 0.9.5 texture and material checks are closed. The NPC
base and eight library characters use 256² maps, cloth/pants use the 4× fabric
weave without baked-AO blotches, the Walk of Fame band uses the paver/terrazzo
texture, the neighbour awning keeps its cream stripe, and the skyline fades into
per-phase haze. Godot is 263/263, Python tooling 101/101, `blender_assets.py
audit`/`fit` and `npc_pipeline.py audit` pass, the 24-NPC headless smoke is
6.94 ms (168 meshes / 131,616 triangles), the 48-frame baseline and 96-frame
final-context review pass, `check_screenshots.sh` is 21 good / 0 bad, and the
screen-design judge is 21/21 match. The optional realism judge ran with
`z-ai/glm-5.3-flash` (mean overall 1.8, Pillow gap 0.306) and its report remains
local; the committed comparison sheet is
`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`. Tagged `v0.9.5`;
Gitea run 922 passed checks and the full build and published seven assets.
## Patch 0.9.6 — Integration, performance and judge gate
Goal: integrate the UI/model/texture patches, prove the combined build on the
target paths and leave a clean release-candidate handoff.
### Integration and asset rebuild
- [x] Rebuild all changed Blender/GLB/AO assets, re-import, and verify manifest/fit/
audit coverage. (The v0.9.5 commit already rebuilt the base, eight characters,
gear and AO maps; this checkpoint re-imported them and re-ran
`blender_assets.py audit`/`fit` and `npc_pipeline.py audit`, all exit 0.)
- [x] Run the complete headless suite, Python tooling and smoke matrix; fix any
regression from the three polish patches. (Godot 263/263, Python 101/101;
14 smokes green: audio, campaign, crowd, emergency, focus/radial, movement,
NPC budget, PLAN verbs, props, scenario content, session shell, timeline,
tutorial and UI flows. Campaign/tutorial used isolated `XDG_DATA_HOME`.)
- [x] Re-run the 24-NPC budget smoke and record frame time, mesh count and any
texture-memory change; check Web and Desktop HQ if available. (24 avatars,
168 meshes, 131,616 triangles, 6.90 ms of 50 ms headless; NPC maps remain
0.48 MB. A clean detached worktree at `f5b8ef2` built Linux/Windows/macOS/
Web/HQ (`161.5/197.1/148.0/97.8/241.5 MB`); the Web zip has index.html,
wasm and pck, the HQ zip has `override.cfg`, and the HQ binary launches
headless with the Forward+ override.)
- [x] Verify save/campaign/tutorial behavior is unchanged by the visual/UI patches.
(Persistence/session tests remain in the 263-test suite; campaign and tutorial
smokes passed with isolated `XDG_DATA_HOME`.)
### Visual gallery and judge gate
- [x] Recapture the full gallery, portraits, venue/realsim stages and Desktop HQ A/B
as needed; no stale screenshots in the release set. (Gallery 21/21,
NPC baseline 48 frames, final-context review 96 frames and a fresh 10-shot
realism run were recaptured. No Forward+ code changed since 0.7.1, so the
HQ A/B was not re-staged; the HQ artifact is checked with the release build.)
- [x] Run `tools/check_screenshots.sh`, `screen_designs.py compare` and
`screen_designs.py judge` on the final captures. (21 good / 0 bad; compare
21 good / 0 bad; judge 21/21 match, 0 review, 0 diff.)
- [x] Run the realism judge for the affected concepts with a pinned model; record
model, attempt count, spend and verdicts. (`z-ai/glm-5.3-flash`, 1 pass per
shot, 10 shots, no fallback/retry: mean overall 2.1, lighting 2.4, materials
2.1, texture 1.7, backdrop 2.3, characters 1.8, Pillow gap 0.291. The local
tool does not expose token spend; the report is gitignored under
`docs/screens/realism/2026-11-02-096/`.)
- [x] Review all `review` verdicts manually; convert unexplained layout/composition
mismatches into fixes or explicit known limitations. (Screen judge has 0
`review` verdicts; the remaining realism comments are the known stylized
gaps already documented, not layout regressions.)
- [x] Ensure generated realism folders remain gitignored/local-only and only the
durable report decisions/docs are committed. (`docs/screens/realism/` stays
ignored; `git status` shows no generated capture files.)
### Release-candidate handoff
- [x] Update README, architecture, development status, changelog and TODO with the
final patch-level results and any remaining gaps. (README/PLAN/architecture
now point at 0.9.6; the changelog and development status record the
integration build, performance numbers, judge results and the clean-worktree
export rule; `docs/RELEASING.md` documents the ignored-folder trap.)
- [x] Tag/version or otherwise freeze `0.9.6` as the integration checkpoint before
the `0.10.0` release candidate. (Tag `v0.9.6` on `4bb2896`; Gitea run 1296
passed checks and the full build and published seven assets.)
- [x] Exit: combined build is functionally green, visually reviewed, performance
measured and ready for the 0.10.0 content-lock/playtest phase.
Completed 2026-11-03: the 0.9.6 integration checkpoint is closed. The current
asset set re-imported and passed `blender_assets.py audit`/`fit` and
`npc_pipeline.py audit`; the full suite is 263/263 with 101/101 Python tests and
14 smokes green; the 24-NPC budget is 6.90 ms with 168 meshes and 131,616
triangles; the gallery and screen-design gate are 21 good / 0 bad and 21/21
match; the pinned realism judge (`z-ai/glm-5.3-flash`) scored mean overall 2.1
with a 0.291 Pillow gap. A clean worktree built Linux/Windows/macOS/Web/HQ at
161.5/197.1/148.0/97.8/241.5 MB; the Web zip and HQ override were checked and
the HQ binary launched headless. Tagged `v0.9.6`; Gitea run 1296 passed checks
and the full build and published seven assets.
+43 -14
View File
@@ -88,7 +88,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
`Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`, and `NpcAvatar` skips the baked per-character
AO on those two slots. The 256² AO was magnifying into large blotches; the fabric now reads as a
fine weave. Skin/hair/gear keep baked AO. Test `test_fabric_uv_scale_avoids_the_baked_ao_pattern`
guards this. Current suite is 263/263.
guards this. Current suite is 272/272.
- **NPC visual baseline (0.9.4, 2026-11-02):** `src/test/capture_npc_baseline.gd` is the
neutral-studio 8-variant × 6-view baseline (front, three-quarter, profile, back, full body,
crowd). It uses a fixed orbit camera, mid-grey studio backdrop, key/fill lights, no HUD/crowd/animation;
@@ -175,14 +175,42 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
Previous/Next disable at the ends; F1 tutorial skip is keyboard-only by design.
- **Version and roadmap renumber (2026-11-02):** source was **0.9.3** at the UI
polish milestone, **0.9.4** after the NPC pass, now **0.9.5** after the texture
and material pass. Next work is **0.9.6 integration**, then the **0.10.0**
minor release candidate. `TODO.md` plans only 0.x patch/minor increments; no
major-version scope is defined yet. The `v0.9.5` tag is the release trigger; no
and material pass, and **0.9.6** as the integration checkpoint. Next work is
the **0.10.0** minor release candidate. `TODO.md` plans only 0.x patch/minor increments; no
major-version scope is defined yet. The `v0.9.6` tag is the release trigger; no
tag was created for 0.9.3.
- **Priority change (2026-11-02):** the map optimization, pre-build NPC revisit,
0.9.3 UI polish, 0.9.4 NPC visual optimization and 0.9.5 texture/material pass
are complete and in `DONE.md`. 0.9.6 integration and 0.10.0 release work are
next. The current source version is 0.9.5.
0.9.3 UI polish, 0.9.4 NPC visual optimization, 0.9.5 texture/material pass and
0.9.6 integration are complete and in `DONE.md`. 0.10.0 release-candidate work
is next. The source version is 0.9.6.
- **Release/legal blockers (0.10.0 work, 2026-11-03):** macOS remains unsigned
(quarantine instructions only); AI image provider terms/commercial rights and
external storefront/rating approval are not cleared; external playtests and the
full hardware matrix are pending. `CREDITS.md`, `PRIVACY.md`, `docs/accessibility.md`
and `docs/known-limitations.md` record these explicitly rather than claiming
approval. Do not tag 0.10.0 as final until these are accepted or cleared.
- **Save migration (0.10.0 work, 2026-11-03):** fixtures in `src/test/fixtures/saves/`
cover 0.8 settings/campaign/history and a 0.9.0 checkpoint. `_load_json` uses
`JSON.new().parse()` so corrupt player files fall back silently instead of
logging a parse error; atomic writes leave the old file intact when the temp
path cannot be written. The migration suite is 9 tests; total 272.
- **0.9.6 integration (2026-11-03):** no gameplay/content changes. A clean
detached worktree at `f5b8ef2` rebuilt Linux/Windows/macOS/Web/HQ without the
dirty tree's ignored `tmp/`/`build/` payload; the in-repo build was 1.6–3.1 GB
per artifact because those folders were packed, so always build release
artifacts from a clean checkout/worktree. Verification: 263/263 tests,
101/101 Python, 14 smokes, 24-NPC 6.90 ms / 168 meshes / 131,616 triangles,
gallery 21/0, screen judge 21/21, realism judge overall 2.1 / gap 0.291.
- **Release v0.9.6 (2026-11-03):** commit `4bb2896` tagged `v0.9.6`; Gitea
Actions run 1296 passed checks and the full build and published seven assets
(Linux, Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page:
`https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.6`. This is
the integration checkpoint before 0.10.0; do not reuse the tag.
- **Release v0.9.5 (2026-11-02):** commit `4bc6390` tagged `v0.9.5`; Gitea Actions run
922 passed checks and the full build and published seven assets (Linux, Windows,
macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page:
`https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.5`. Do not
reuse the tag; future fixes need a new patch tag.
- **Release v0.9.4 (2026-11-02):** commit `a57b86c` tagged `v0.9.4`; Gitea Actions
run 916 passed checks and the full build and published seven assets (Linux,
Windows, macOS, NSIS setup, AppImage, Web, Desktop HQ). Release page:
@@ -824,7 +852,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.
- 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()`.
- Headless suite is **263/263**; Python tooling is **101/101**; 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`.
- Headless suite is **272/272**; Python tooling is **101/101**; 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).
- 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
@@ -847,8 +875,9 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
## Plan vs code
`PLAN.md` describes intended design; `README.md` and `DONE.md` describe what shipped. As of
2026-11-02 the playable build covers the three venues, tutorials/campaign/checkpoints, the 0.9.3
UI/accessibility pass, the 0.9.4 NPC visual optimization, the 0.9.5 texture/material pass, and the Blender + realism asset pipeline. `TODO.md` holds the remaining
2026-11-03 the playable build covers the three venues, tutorials/campaign/checkpoints, the 0.9.3
UI/accessibility pass, the 0.9.4 NPC visual optimization, the 0.9.5 texture/material pass, the
0.9.6 integration checkpoint, and the Blender + realism asset pipeline. `TODO.md` holds the remaining
0.9.6–0.10.0 work. The project uses a custom headless runner, not GUT, and Gitea Actions, not
GitHub Actions.
@@ -856,7 +885,7 @@ GitHub Actions.
## Uncommitted Workstreams
- **None (2026-11-01).** The NPC detail pass, the realism follow-ups and the flashlight beam
decision are committed together with the portrait capture and the refreshed screenshots.
Keep the rule from `AGENTS.md`: check `git status` before committing, and never revert
another contributor's in-flight files.
- **None (2026-11-03).** The 0.9.6 integration checkpoint is committed, tagged
`v0.9.6` and published with seven assets; the workspace is clean apart from
local/gitignored realism and capture output. Keep the rule from `AGENTS.md`: check `git status`
before committing, and never revert another contributor's in-flight files.
+1 -1
View File
@@ -176,7 +176,7 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD
## Versioning and Delivery
`config/version` in `project.godot` is the single version source (currently `0.9.5`). 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.6`). 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
+27
View File
@@ -0,0 +1,27 @@
# Privacy Note
Gig Line runs locally. The game itself does not include telemetry, analytics,
accounts, advertising, cloud saves or network requests. It does not access the
camera, microphone, contacts or location, and the body-camera feature is a
simulated overlay, not a real recording device.
Player data is written only to Godot's local `user://` directory:
- `settings.json`
- `campaign.json`
- `shift_results.json`
- `shift_state.json`
Those files stay on the player's machine unless the player copies them. Deleting
the files (or the `Gig Line` user directory) resets the profile. See
[`docs/getting-started.md`](docs/getting-started.md#save-data-and-migration) for
locations, migration and rollback instructions.
The repository's development tools can call OpenRouter when a developer supplies
`OPENROUTER_API_KEY`; those calls generate or judge development assets and are
not part of the shipped game. The key and generated review artifacts remain
local/gitignored.
Questions about a storefront release should be checked against the final package
and the provider terms recorded in [`CREDITS.md`](CREDITS.md); external legal and
storefront approval is still pending for 0.10.0.
+24 -9
View File
@@ -38,13 +38,14 @@ precision walk, the Joke Shop's Sunset Strip frontage, the TV Studio arrival, an
> Downloads are public (no sign-in required) since the host's instance-wide
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
>
> **Latest published: v0.9.5** — the texture and material refinement pass:
> 256² NPC maps, a four-times fabric weave without baked AO blotches, repair of
> the Walk of Fame terrazzo band and awning stripe, and per-phase skyline haze,
> on top of the v0.9.4 NPC visual optimization, the Hollywood/Joke Shop maps and
> the v0.9.3 UI/accessibility polish.
> **Latest published: v0.9.6** — the integration checkpoint: the 0.9.5 texture
> and material refinement (256² NPC maps, a four-times fabric weave without baked
> AO blotches, repaired Walk of Fame/awning surfaces and per-phase skyline haze)
> is packaged with the v0.9.4 NPC visual optimization, the Hollywood/Joke Shop
> maps and the v0.9.3 UI/accessibility polish, with the full gallery, smoke matrix
> and Web/Desktop HQ build re-verified.
>
> **Current source: v0.9.5** — tagged and built from the same commit.
> **Current source: v0.9.6** — tagged and built from the same commit.
> Releases are built automatically when a `vX.Y.Z` tag is pushed
> (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use
> `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline
@@ -120,17 +121,21 @@ Full setup, every run/test/smoke command, and the controls table are in
- [`docs/asset-tooling.md`](docs/asset-tooling.md) — Asset pipelines, model tools, and screenshot gates.
- [`docs/getting-started.md`](docs/getting-started.md) — Setup, run/test commands, and controls.
- [`docs/development-status.md`](docs/development-status.md) — Implemented features and what's next.
- [`docs/accessibility.md`](docs/accessibility.md) — Accessibility features, known gaps and statement.
- [`docs/known-limitations.md`](docs/known-limitations.md) — Release-candidate blockers and accepted limitations.
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md) — Manual smoke-test checklist.
- [`CREDITS.md`](CREDITS.md) — Credits, AI/reference provenance and licensing notes.
- [`PRIVACY.md`](PRIVACY.md) — Local-only data, save files and development API use.
- [`docs/blender-assets.md`](docs/blender-assets.md) — Blender asset library: characters, gear, props, attach points.
- [`docs/npc-visual-review.md`](docs/npc-visual-review.md) — 0.9.4 NPC baseline, acceptance criteria and ranked visual gaps; 0.9.5 texture comparison is in `docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`.
- [`docs/npc-visual-review.md`](docs/npc-visual-review.md) — 0.9.4 NPC baseline, acceptance criteria and ranked visual gaps; 0.9.5 texture comparison is in [`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`](docs/blender_previews/sheet_texture_refinement_0.9.5.jpg).
- [`docs/npc-proportion-audit.md`](docs/npc-proportion-audit.md) — NPC proportion measurements against the shared stylized guide.
- [`docs/adr/`](docs/adr/) — Architecture Decision Records.
- [`TODO.md`](TODO.md) — Active tasks plus the 0.x gap analysis and milestone roadmap.
## Development Status
v0.9.5 is the current source version: the completed texture and material
refinement (256² NPC maps, a 4× fabric weave with baked-AO removal, repaired
v0.9.6 is the current source version: the integrated texture/material and UI
checkpoint on top of the 0.9.5 texture refinement (256² NPC maps, a 4× fabric weave with baked-AO removal, repaired
Walk of Fame/awning surfaces and per-phase skyline haze), on top of the v0.9.4
NPC visual optimization and the v0.9.3 UI/accessibility polish pass. It builds on
v0.9.2's Hollywood/Joke Shop map rebuilds, v0.9.1's Godot 4.7.2 baseline,
@@ -139,6 +144,16 @@ tutorial/campaign/resume work and the v0.7.x venue/gear/judge work. See [`docs/d
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).
## Credits, Privacy and Accessibility
Gig Line is a local-only single-player game: there is no runtime telemetry or
network service, and saves stay in Godot's local `user://` folder. The
body-camera overlay is simulated. Asset provenance, AI-assisted image usage and
credits are recorded in [`CREDITS.md`](CREDITS.md); the local-data policy is in
[`PRIVACY.md`](PRIVACY.md); accessibility features and gaps are in
[`docs/accessibility.md`](docs/accessibility.md); and release-candidate blockers
are listed in [`docs/known-limitations.md`](docs/known-limitations.md).
## Development Workflow
Follow [`AGENTS.md`](AGENTS.md): inspect the repository first, record work as checkbox tasks in `TODO.md`, move completed sections to `DONE.md`, update `MEMORY.md` with durable context, and keep this README synchronized with setup and architecture changes.
+67 -128
View File
@@ -5,9 +5,9 @@ see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these
## Gap Analysis and 0.x Roadmap
Baseline: **v0.9.5** (2026-11-02). Three playable venues, tutorial/campaign/checkpoint resume,
Baseline: **v0.9.6** (2026-11-03). Three playable venues, tutorial/campaign/checkpoint resume,
difficulty/help, audio/accessibility settings, a 24-NPC budget baseline, a complete
escalation/access/radio loop, shift review, the Blender asset library, 263 headless + 101 Python
escalation/access/radio loop, shift review, the Blender asset library, 272 headless + 101 Python
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.
@@ -45,120 +45,23 @@ release-candidate foundation, not yet a 0.10.0 release candidate.
| 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, the **pre-build NPC revisit**, the
**0.9.3 UI polish**, **0.9.4 NPC visual optimization** and **0.9.5 texture
refinement** passes are complete (all moved to `DONE.md` on 2026-11-02).
0.9.6 integration and the 0.10.0 release candidate are queued. The source
version is now 0.9.5.
**0.9.3 UI polish**, **0.9.4 NPC visual optimization**, **0.9.5 texture
refinement** and **0.9.6 integration** passes are complete (all moved to
`DONE.md`). The **0.10.0 release candidate** is next: content lock, external
playtest, balance, save migration and release/legal sign-off. The source version
is 0.9.6; the v0.9.6 release is published.
## Patch 0.9.5 — Texture and material refinement
Goal: improve close-range surface and character texture quality while keeping GL
Compatibility/Web parity, deterministic assets and offline CI.
### Environment textures and materials
- [x] Audit the v2 picks (brick, sidewalk pavers, dry/wet asphalt, awning canvas,
day/dusk/night skylines) at close range and in the entrance/street shots for
seams, tiling, scale, contrast and normal-map strength. (New `surfaces` and
`surfaces_joke` capture stages; no seam or UV-scale failures found.)
- [x] Refine brick mortar/wear, paver joints/stains, asphalt aggregate/wet response
and awning weave using the existing image/asset tooling; never overwrite a
pick, use `-vN` candidates and promote validated results. (The Walk of Fame
band now uses the paver/terrazzo texture; the neighbour awning keeps its
cream stripe via a neutral tint and 4× UV. Brick/asphalt picks held up.)
- [x] Check UV scales and material roughness/metallic profiles; keep day/night
phase behavior and the wet-street transition intact. (Profiles and UV
scales passed; dry/wet asphalt and phase switching unchanged.)
- [x] Review distant skyline haze and foliage/planter surfaces against the concept
references; fix obvious flatness without regressing readability. (Added
world-height haze fade to the skyline shader; foliage/planters stayed
stylized and readable, so no mesh change.)
### Character and prop textures
- [x] Audit NPC skin, cloth, pants, hair and gear albedo/normal/AO maps in portraits;
refine fabric weave, skin variation and fixed detail materials. (256² maps rebuilt; fabric
now uses a 4× UV weave and skips baked per-character AO, removing the portrait blotches;
skin/hair/gear keep AO. Comparison `sheet_npc_baseline_compare-texuv.jpg`.)
- [x] Re-bake AO where geometry changes; verify the 256² maps and material wiring.
(No character geometry changed in the texture pass; asset/base audits pass.)
- [x] Audit library props and UI/card/icon textures at their actual screen sizes;
remove blurry/oversized or unused textures where safe. (All 21 screen-art
textures are 1280×720 and referenced; access cards are 256×128, HUD icons
64×64; no oversized or unused runtime texture found. Legacy root textures
remain only as the `generate_textures.py`/asset-test fallback.)
- [x] Run texture audits, import, and the full asset test suite; record texture memory
and triangle/draw-call impact. (Godot 263/263, Python 101/101; asset and base
audits pass; 24 NPCs = 168 meshes / 131,616 triangles / 6.94 ms. Compressed
payloads: picks 24.55 MB, UI art 22.67 MB, NPC maps 0.48 MB.)
### Visual verification
- [x] Run `python3 tools/scene_concepts.py realism` for the affected stages with the
judge enabled; record metric gaps and model/spend. (Run
`docs/screens/realism/2026-11-02-tex` with `z-ai/glm-5.3-flash`, one pass per
shot; mean `after` overall 1.8, lighting 2.3, materials 1.8, texture 1.2,
backdrop 2.0, characters 1.2, Pillow gap 0.306. The judge is optional and not
a CI gate; the report stays local.)
- [x] Re-run `tools/check_screenshots.sh` and the screen-design judge; clear all
`diff` verdicts. (21 good / 0 bad; `screen_designs.py judge` = 21/21 match,
0 review, 0 diff.)
- [x] Capture before/after close-ups for brick, pavement, asphalt, character cloth
and skyline; document accepted trade-offs. (Committed
`docs/blender_previews/sheet_texture_refinement_0.9.5.jpg`: Walk of Fame
pavers, asphalt aggregate, limestone facade, day skyline haze and NPC cloth;
asphalt/limestone picks intentionally held, no replacement.)
- [x] Update asset docs and the changelog/status with the promoted texture versions.
(README/PLAN/architecture/status/MEMORY and the 0.9.5 changelog entry now
carry the texture results; the 0.9.5 section moved to `DONE.md` after the
release build.)
## Postponed — Patch 0.9.6 — Integration, performance and judge gate
Goal: integrate the UI/model/texture patches, prove the combined build on the
target paths and leave a clean release-candidate handoff.
### Integration and asset rebuild
- [ ] Rebuild all changed Blender/GLB/AO assets, re-import, and verify manifest/fit/
audit coverage.
- [ ] Run the complete headless suite, Python tooling and smoke matrix; fix any
regression from the three polish patches.
- [ ] Re-run the 24-NPC budget smoke and record frame time, mesh count and any
texture-memory change; check Web and Desktop HQ if available.
- [ ] Verify save/campaign/tutorial behavior is unchanged by the visual/UI patches.
### Visual gallery and judge gate
- [ ] Recapture the full gallery, portraits, venue/realsim stages and Desktop HQ A/B
as needed; no stale screenshots in the release set.
- [ ] Run `tools/check_screenshots.sh`, `screen_designs.py compare` and
`screen_designs.py judge` on the final captures.
- [ ] Run the realism judge for the affected concepts with a pinned model; record
model, attempt count, spend and verdicts.
- [ ] Review all `review` verdicts manually; convert unexplained layout/composition
mismatches into fixes or explicit known limitations.
- [ ] Ensure generated realism folders remain gitignored/local-only and only the
durable report decisions/docs are committed.
### Release-candidate handoff
- [ ] Update README, architecture, development status, changelog and TODO with the
final patch-level results and any remaining gaps.
- [ ] Tag/version or otherwise freeze `0.9.6` as the integration checkpoint before
the `0.10.0` release candidate.
- [ ] Exit: combined build is functionally green, visually reviewed, performance
measured and ready for the 0.10.0 content-lock/playtest phase.
## Postponed — Milestone 0.10.0 — Release candidate
## Milestone 0.10.0 — Release candidate
Goal: freeze the feature set, prove it with external players, clear release/legal blockers and
verify every distribution path. This is the next minor release after the 0.9.x patch line.
### Content lock and external playtest
- [ ] Declare a content/feature freeze for 0.10.0; only release-blocking fixes and balance changes
may land after the freeze.
- [x] Declare a content/feature freeze for 0.10.0; only release-blocking fixes and balance changes
may land after the freeze. (Frozen 2026-11-03 after the 0.9.6 integration
checkpoint; this pass is limited to release reliability, accessibility,
balance evidence and packaging.)
- [ ] Produce a release-candidate build with the tutorial, campaign, all three venues, Help,
accessibility settings and checkpoint/resume enabled.
- [ ] Run at least five first-time-player sessions covering the tutorial and one full venue shift;
@@ -183,16 +86,31 @@ verify every distribution path. This is the next minor release after the 0.9.x p
### Save migration and session reliability
- [ ] Add fixtures for a 0.8-era settings/campaign/history save and a 0.9.0 checkpoint; verify
migration/default-filling and a clean first launch with no files.
- [ ] Test fresh install → tutorial → campaign → pause → quit → resume → review → next venue on
an isolated user-data directory.
- [ ] Test corrupt JSON, partial temp files, missing optional fields and a failed atomic rename;
confirm fallback behavior never destroys a valid save.
- [ ] Verify checkpoint resume after all three venues, after an incident, with NPCs at the door and
with flashlight/body-camera/door state set.
- [ ] Document save locations, schema versions, reset behavior and rollback/export instructions in
`docs/getting-started.md` and the release notes.
- [x] Add fixtures for a 0.8-era settings/campaign/history save and a 0.9.0 checkpoint; verify
migration/default-filling and a clean first launch with no files. (Committed
fixtures under `src/test/fixtures/saves/`; 9 migration/failure tests cover
old settings/campaign/history, the 0.9.0 checkpoint, all three venue
checkpoint round-trips, clean install, corrupt JSON and a blocked atomic
replace. Suite is now 272 tests.)
- [x] Test fresh install → tutorial → campaign → pause → quit → resume → review → next venue on
an isolated user-data directory. (Tutorial and campaign smokes shared one
isolated `XDG_DATA_HOME`; tutorial completion/replay/skip then campaign
resume/review-continue/help both passed. Pause/menu behavior is covered by
`smoke_session_shell.gd`.)
- [x] Test corrupt JSON, partial temp files, missing optional fields and a failed atomic rename;
confirm fallback behavior never destroys a valid save. (Corrupt/partial files
fall back to defaults; a blocked temp path reports an error and leaves the
prior valid settings intact. `_load_json` now uses the non-logging JSON
instance parser.)
- [x] Verify checkpoint resume after all three venues, after an incident, with NPCs at the door and
with flashlight/body-camera/door state set. (The migration suite round-trips a
checkpoint for Hollywood, the Joke Shop and the TV Studio with an open
incident, one NPC row, flashlight/body-camera/door flags; the campaign smoke
resumes a live checkpoint through the real `GameManager`.)
- [x] Document save locations, schema versions, reset behavior and rollback/export instructions in
`docs/getting-started.md` and the release notes. (`getting-started.md` now
documents the four files, version-1 checkpoint, atomic write/fallback,
clean install, reset and backup/rollback.)
### Release engineering and distribution
@@ -200,22 +118,43 @@ verify every distribution path. This is the next minor release after the 0.9.x p
artifact names, sizes and checksums.
- [ ] Smoke-test fresh Linux portable/AppImage, Windows portable/installer, macOS unzip/launch,
Web static hosting and Desktop HQ Vulkan fallback on the target matrix.
- [ ] Add a disk-space preflight and retention policy to the release/publish scripts so a full
Gitea host cannot leave a partially uploaded release.
- [ ] Verify release metadata, tag/version consistency, asset counts and download links; retain
- [x] Add a disk-space preflight and retention policy to the release/publish scripts so a full
Gitea host cannot leave a partially uploaded release. (`publish_release.sh`
checks artifact size/free space, writes SHA-256 checksums, supports
`PUBLISH_DRY_RUN`, rolls back uploaded assets/new releases on failure, and
offers opt-in `RETAIN_RELEASES` pruning; dry-run verified.)
- [x] Verify release metadata, tag/version consistency, asset counts and download links; retain
at least the latest 0.8/0.9 releases and document older-asset pruning.
- [ ] Decide and document whether macOS signing/notarization is required for 0.10.0 or explicitly
(Source/version and tag are both 0.9.6; all seven published releases carry
5–7 assets, including the retained v0.8.0/v0.8.1/v0.9.0/v0.9.1 line.
`publish_release.sh` documents opt-in `RETAIN_RELEASES` pruning and
`RELEASING.md` describes the seven-release policy.)
- [x] Decide and document whether macOS signing/notarization is required for 0.10.0 or explicitly
remains a future release blocker; record the quarantine instructions either way.
(0.10.0 ships unsigned with quarantine instructions; signing/notarization
remains an explicit public-release blocker in `RELEASING.md` and
`known-limitations.md`.)
### Legal, credits and platform policy
- [ ] Audit AI-assisted asset provenance and licenses; record source/model usage and any
- [x] Audit AI-assisted asset provenance and licenses; record source/model usage and any
redistribution constraints for textures, characters, props and UI art.
- [ ] Audit audio/SFX/music licensing (or confirm everything is procedural) and add a credits file.
- [ ] Review body-camera/privacy language, mental-health-crisis representation, age/content rating
(`CREDITS.md` records the Blender/OpenRouter toolchain, default model slugs,
missing per-file generation metadata and the commercial-rights review as an
explicit release blocker.)
- [x] Audit audio/SFX/music licensing (or confirm everything is procedural) and add a credits file.
(All runtime audio is procedural; no external music/SFX files are bundled.
`CREDITS.md` records this.)
- [x] Review body-camera/privacy language, mental-health-crisis representation, age/content rating
and storefront requirements; fix any release-blocking copy or content.
- [ ] Add the player-facing credits, known limitations, accessibility statement and privacy note to
the release package/README.
(`PRIVACY.md` clarifies the simulated body camera/local-only data; the
accessibility and known-limitations pages record the remaining
mental-health/rating/storefront review as a release blocker rather than
claiming approval.)
- [x] Add the player-facing credits, known limitations, accessibility statement and privacy note to
the release package/README. (`README.md` links `CREDITS.md`, `PRIVACY.md`,
`docs/accessibility.md` and `docs/known-limitations.md`; the Web and Desktop
HQ package builders copy those documents into the shipped archives.)
- [ ] Mark external legal/storefront approval as complete or explicitly block the 0.10.0 release on it.
### Documentation and visual sign-off
+4
View File
@@ -14,7 +14,11 @@ This folder holds the project documentation, visual references and generated QA
- [`npc-visual-review.md`](npc-visual-review.md) — NPC visual baseline, acceptance criteria and ranked gaps
- [`npc-proportion-audit.md`](npc-proportion-audit.md) — NPC proportion ratios and stylized guide results
- [`development-status.md`](development-status.md) — implemented features and current gaps
- [`accessibility.md`](accessibility.md) — accessibility statement and known gaps
- [`known-limitations.md`](known-limitations.md) — release-candidate blockers and accepted limits
- [`smoke-checklist.md`](smoke-checklist.md) — manual verification
- [`../CREDITS.md`](../CREDITS.md) — credits and asset provenance
- [`../PRIVACY.md`](../PRIVACY.md) — privacy note
- [`ui-copy.md`](ui-copy.md) — screen, copy and button inventory
- [`RELEASING.md`](RELEASING.md) — export, CI and publishing workflow
- [`adr/README.md`](adr/README.md) — architecture decisions
+25
View File
@@ -103,6 +103,31 @@ The optional Desktop HQ zip is assembled by `tools/build_releases.sh` from
`packaging/desktop_hq/`; keep `override.cfg` next to the extracted executable.
It is not part of the default Compatibility/Web build.
`tools/publish_release.sh` preflights every artifact for existence and non-zero
size, checks free space in `build/`, writes `build/SHA256SUMS-<version>.txt`, and
uploads only after those checks pass. If an upload or the final asset-count check
fails, it removes the assets it just uploaded (and removes a newly created
release) so the host never keeps a partial release. Use
`PUBLISH_DRY_RUN=1 ./tools/publish_release.sh` to run the preflight and checksum
steps without touching the API. Set `RETAIN_RELEASES=7` to prune older releases
after a successful publish; the default `0` disables pruning and never deletes
history implicitly. A retention of seven keeps the newest seven releases,
including the latest 0.8/0.9 line.
### 0.10.0 macOS signing decision
The 0.10.0 release candidate will continue to ship an **unsigned** macOS zip with
the documented right-click/`xattr` quarantine instructions. Signing and
notarization remain a public-release blocker until an Apple Developer account and
a reproducible signing/notarization job are available; they are not silently
claimed as complete. The same limitation is recorded in
[`known-limitations.md`](known-limitations.md).
Always run the release build from a clean checkout or detached worktree. Godot
can pack files from ignored `tmp/` and old `build/` folders into the PCK; the
0.9.6 dirty-tree build produced 1.6–3.1 GB artifacts, while the clean worktree
produced the expected ~160–240 MB packages.
Locally you can build the AppImage with `bash packaging/linux/build_appimage.sh <version>`;
the NSIS installer requires `makensis` (covered by CI).
+32
View File
@@ -0,0 +1,32 @@
# Accessibility Statement
Gig Line is a keyboard/mouse-first first-person game with gamepad support. The
current build includes the following accessibility features:
- Remappable keyboard and gamepad bindings, persisted in `settings.json`.
- Keyboard focus rings and explicit default focus in every modal, with A/B
accept/cancel on a gamepad and wrapped Tab/arrow navigation.
- Adjustable HUD font scale up to 1.75× with clipped/ellipsized long labels.
- Reduced-motion mode that disables head bob and motion-sensitive presentation.
- Captions in the HUD log for radio, interaction and event messages.
- Master, SFX and ambience volume controls.
- Shape plus number escalation meter, so escalation is readable without colour.
- Hold-to-confirm emergency action and guarded destructive actions.
- Searchable Help generated from the live input bindings.
- Mouse-transparent HUD, right-click/Esc modal handling, and a predictable
top-panel close path.
Known gaps:
- There is no screen-reader integration or text-to-speech narration.
- The game is English-only; no pseudo-locale or full localization review has
been completed.
- The visual style is stylized and some small environmental detail may be hard
to read at low resolution; a full low-vision/high-contrast audit is pending.
- Audio captions cover gameplay messages, not every ambient sound.
- A formal WCAG conformance claim has **not** been made. External accessibility
review is still a 0.10.0 release-review item.
Test evidence and the manual checklist live in
[`smoke-checklist.md`](smoke-checklist.md); settings and save behaviour are
documented in [`getting-started.md`](getting-started.md).
+1 -1
View File
@@ -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.
- **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.5`). 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.6`). 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
+13
View File
@@ -268,6 +268,19 @@ photoreal renderer. The 0.9.5 before/after close-up sheet is
the texture-adoption run itself was the earlier 0.8-style candidate comparison,
and its report remains local/gitignored.
For the 0.9.6 integration checkpoint the same judge was pinned to
`z-ai/glm-5.3-flash` and run once per shot on the 10-shot set: mean overall 2.1,
lighting 2.4, materials 2.1, texture 1.7, backdrop 2.3, characters 1.8, Pillow
gap 0.291. No `review` verdicts were produced by the screen judge, so there was
no manual layout verdict to convert into a fix; the remaining realism comments
are the accepted stylized gaps.
Build release artifacts from a clean checkout or detached worktree. A normal
working tree may contain ignored `tmp/` captures and old `build/` artifacts, and
Godot can pack enough of those into exports to inflate a 0.9.6 package from
~160 MB to 1.6–3.1 GB. The 0.9.6 clean worktree produced 161.5 MB Linux,
197.1 MB Windows, 148.0 MB macOS, 97.8 MB Web and 241.5 MB Desktop HQ.
The Forward+ A/B compared the Compatibility entrance with the optional Desktop HQ
preset. The gear-fit run checked the sunglasses, bag and helmet against measured
head/skirt bounds with `python3 tools/blender_assets.py fit`; it also fixed the
+5 -1
View File
@@ -5,7 +5,7 @@ Implemented so far:
- Godot 4.7.2 project scaffold, input map, procedural world builder (CI/local/release unified)
- Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`
- Three authored scenarios (El Capitan Entertainment Centre / Hollywood Boulevard, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools
- Headless unit tests: **263 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings)
- Headless unit tests: **272 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings)
- Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event
- First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review
- Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`)
@@ -42,6 +42,10 @@ Implemented so far:
- NPC neck/shoulder transition (0.9.4 work): the body graph now uses a fuller neck base, a slightly tapered neck top and a lower shoulder root, giving the head/neck/shoulder line a trapezius slope instead of a thin post. The full character library was rebuilt with a final degenerate-face cleanup; the baseline comparison is `sheet_npc_baseline_compare-body.jpg`.
- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
- NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`.
- Release v0.9.6 (2026-11-03): the tagged commit `4bb2896` passed Gitea Actions run 1296 (checks + full build) and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.6>.
- Save migration and reliability (0.10.0 work): committed 0.8-era settings/campaign/history fixtures and a 0.9.0 checkpoint. The loader default-fills old settings, keeps campaign progress and validates checkpoints for all three venues with an open incident, NPC rows, door, flashlight and body-camera state. Corrupt and partial files fall back to defaults; a blocked atomic replace leaves the prior valid file intact. `_load_json` uses the non-logging JSON parser; save locations and rollback are documented in `getting-started.md`. Suite is 272/272.
- Integration checkpoint (0.9.6 work): the clean worktree at `f5b8ef2` rebuilt all five release packages and the Desktop HQ zip without ignored `tmp/`/`build/` content (Linux 161.5 MB, Windows 197.1 MB, macOS 148.0 MB, Web 97.8 MB, HQ 241.5 MB). The Web zip contains `index.html`, `index.wasm` and `index.pck`; the HQ zip contains `override.cfg` and launches headless with the Forward+ override. Current verification: Godot 263/263, Python 101/101, 14 smokes green, 24-NPC budget 6.90 ms / 168 meshes / 131,616 triangles, gallery 21 good / 0 bad, screen judge 21/21 match with 0 review/diff, and a pinned realism judge (`z-ai/glm-5.3-flash`, one pass per shot) at mean overall 2.1 and Pillow gap 0.291. No new gameplay or content changes.
- Release v0.9.5 (2026-11-02): the tagged commit `4bc6390` passed Gitea Actions run 922 (checks + full build) and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.5>.
- Release v0.9.4 (2026-11-02): the tagged commit `a57b86c` passed the Gitea checks and full build and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4>.
- Environment texture pass (0.9.5 work): `capture_realism.gd` now has hidden-HUD `surfaces`/`surfaces_joke` close-up stages. The Walk of Fame band uses the paver texture as dark terrazzo, the neighbour awning keeps its cream stripe, and the skyline shader blends its lower 42 m toward a per-phase haze colour. Brick, asphalt, limestone and paver UV scales/profiles passed without replacement.
- Texture/prop audit (0.9.5 work): all 21 screen-art textures are 1280×720 and referenced; access cards are 256×128 and HUD icons 64×64. No oversized or unused runtime texture was found; the legacy root textures remain only as the texture-generator/asset-test fallback. The 24-NPC smoke now reports 168 meshes, 131,616 triangles and 6.94 ms (50 ms budget); compressed payload is 24.55 MB of surface picks, 22.67 MB of screen art and 0.48 MB of NPC maps.
+26
View File
@@ -74,6 +74,32 @@ XDG_DATA_HOME=/tmp/gigline-campaign godot --path . --fixed-fps 60 --script res:/
The unit test runner is the project's own lightweight harness (no editor plugin required) so CI can run it without the editor.
## Save Data and Migration
The game keeps four small JSON files in Godot's `user://` directory, which is
`~/.local/share/godot/app_userdata/Gig Line/` on Linux,
`%APPDATA%\Godot\app_userdata\Gig Line\` on Windows and
`~/Library/Application Support/Godot/app_userdata/Gig Line/` on macOS:
| File | Contents |
| --- | --- |
| `settings.json` | Camera, HUD, binding, difficulty, audio, accessibility and tutorial flags. Missing keys are filled from the current defaults; there is no schema version because the field set grew compatibly. |
| `campaign.json` | Campaign index, completed venues, difficulty, shift number and last grade. Older files with missing fields open at the safe defaults. |
| `shift_results.json` | Completed-shift review history, appended as an array. |
| `shift_state.json` | Resumable mid-shift checkpoint, version `1`. It stores the venue, guard/door/flashlight/body-camera state, coordinator snapshot and NPC rows. |
Writes go to a sibling `.tmp` file and are renamed only after the complete JSON
is written. A corrupt or partial file is ignored on load, so the player gets
defaults rather than a crash and a valid old file is not overwritten by a failed
replace. The migration fixtures used by the tests live in
`src/test/fixtures/saves/`.
For a clean first launch, delete the four files (or the whole `Gig Line` user
directory). Settings' **Reset campaign** clears campaign progress separately.
To back up or move a profile, copy those files while the game is closed; to roll
back, restore the previous copies. Shift history is diagnostic and is not
required to resume a checkpoint.
## Controls
Keyboard and mouse are the default; a gamepad works without configuration (left stick moves,
+48
View File
@@ -0,0 +1,48 @@
# Known Limitations
This page records the limitations that remain at the 0.10.0 release-candidate
stage. Items marked **blocker** must be cleared or explicitly accepted before a
public release.
## Release blockers
- **External playtest:** at least five first-time-player sessions and the
resulting onboarding/accessibility triage have not been completed. The
tutorial/campaign evidence is currently automated smoke coverage, not player
observation.
- **AI asset provenance:** the image-generation provider terms and commercial
redistribution rights for the committed AI-assisted textures/UI art still need
legal review. See [`../CREDITS.md`](../CREDITS.md).
- **Storefront/legal approval:** age/content rating, storefront policy, trademark
language and the mental-health-crisis/body-camera representation have not been
externally approved.
- **macOS signing/notarization:** the 0.10.0 release candidate ships unsigned
with right-click/`xattr` quarantine instructions. Signing and notarization are
required before a broad public/storefront release and remain blocked until an
Apple Developer account and signing job exist.
- **Hardware matrix:** Web, Windows and Desktop HQ have been built and checked
structurally; a complete target-hardware performance/launch matrix is still
pending.
## Content and presentation
- The venues are stylized reconstructions, not survey-accurate models. The Joke
Shop is a fictional stand-in for the real Sunset Strip block.
- Character faces, hands and fabric use a deliberately low-poly style; the
>26 m tier remains a two-quad impostor and there is no separate finger-curl
rig. The accepted limits are recorded in
[`npc-visual-review.md`](npc-visual-review.md).
- There is no live 3D body-camera replay; the timeline is text-based.
- There is no recorded voice/music track. Ambience and cues are procedural.
- The game is English-only and does not yet have a translator workflow or
pseudo-locale layout sign-off.
## Persistence and platform
- Save migration is covered by synthetic 0.8/0.9 fixtures and failure-mode tests,
not by a real player profile collected from every earlier release.
- Desktop HQ requires a Vulkan-capable GPU/driver; the default Compatibility and
Web builds remain the broad target.
- The manual smoke checklist is completed by developers; external device
coverage (Windows installer, macOS launch, gamepad and Web hosts) is not yet a
complete evidence set.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 KiB

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 KiB

After

Width:  |  Height:  |  Size: 659 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 KiB

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 761 KiB

After

Width:  |  Height:  |  Size: 761 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 567 KiB

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 529 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.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+1 -1
View File
@@ -12,7 +12,7 @@ config_version=5
config/name="Gig Line"
config/description="Workplace security simulation about observation, access verification, and proportionate escalation."
config/version="0.9.5"
config/version="0.9.6"
run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.7", "GL Compatibility")
+7 -2
View File
@@ -14,8 +14,13 @@ func _load_json(file_name: String) -> Variant:
var text := FileAccess.get_file_as_string(path)
if text.is_empty():
return null
var parsed = JSON.parse_string(text)
return parsed
# JSON.parse_string logs an engine error on malformed player files. Use the
# instance parser so a corrupt save silently falls back to defaults instead
# of looking like a crash in the console.
var json := JSON.new()
if json.parse(text) != OK:
return null
return json.data
func _save_json(file_name: String, data: Variant) -> Error:
var path := "user://" + file_name
+12
View File
@@ -0,0 +1,12 @@
# Test Fixtures
Committed JSON fixtures used by persistence and migration tests.
- [`saves/settings_0_8.json`](saves/settings_0_8.json) — a 0.8-era settings file missing the later volume, motion and caption keys; loading it must keep the old values and fill the new defaults.
- [`saves/campaign_0_8.json`](saves/campaign_0_8.json) — a 0.8-era campaign after the Hollywood shift; loading it must resolve the Joke Shop as the next venue.
- [`saves/shift_results_0_8.json`](saves/shift_results_0_8.json) — a 0.8-era shift history with two completed venues.
- [`saves/shift_state_0_9_0.json`](saves/shift_state_0_9_0.json) — a version-1 0.9.0 checkpoint at the Joke Shop with door, flashlight and body-camera state set.
These are deliberately schema-less where the old game was schema-less; the
loader fills missing fields from the current defaults rather than rewriting the
player's file on load.
+11
View File
@@ -0,0 +1,11 @@
{
"index": 1,
"next_venue": "joke_shop",
"shift_number": 2,
"difficulty": "hard",
"complete": false,
"completed": [
"hollywood_building"
],
"last_grade": "B"
}
+15
View File
@@ -0,0 +1,15 @@
{
"look_sensitivity": 0.0031,
"hud_font_scale": 1.15,
"invert_y": true,
"head_bob": false,
"input_bindings": {
"inspect": {
"type": "key",
"code": 90
}
},
"ai_difficulty": "hard",
"tutorial_completed": true,
"tutorial_skipped": false
}
+20
View File
@@ -0,0 +1,20 @@
[
{
"scenario_id": "hollywood_building",
"reputation": {
"rating": 81.0,
"grade": "B"
},
"spawned_count": 6,
"completed_at_ms": 1000
},
{
"scenario_id": "joke_shop",
"reputation": {
"rating": 74.5,
"grade": "C"
},
"spawned_count": 5,
"completed_at_ms": 2000
}
]
+26
View File
@@ -0,0 +1,26 @@
{
"version": 1,
"scenario_id": "joke_shop",
"guard_position": [0.0, 0.1, 10.5],
"guard_yaw": 1.25,
"head_pitch": 0.1,
"door_locked": true,
"flashlight_on": true,
"body_camera_on": true,
"npc_counter": 7,
"coordinator": {
"duration_s": 900.0,
"elapsed_s": 540.0,
"current_phase": 3,
"phase_offset_s": 700.0,
"spawned_count": 7,
"responder_outcomes": [],
"investigations": [],
"recorder": {},
"reputation": {},
"crowd": {},
"radio": {},
"scheduler": {}
},
"npcs": []
}
+248
View File
@@ -0,0 +1,248 @@
extends RefCounted
## Persistence migration and failure-mode tests. The fixtures represent a
## 0.8-era player profile and a 0.9.0 checkpoint; loading them must fill current
## defaults without rewriting or losing the old fields.
const Campaign := preload("res://src/core/campaign.gd")
const ScenarioFactory := preload("res://src/content/scenario_factory.gd")
const ShiftCoordinator := preload("res://src/core/shift_coordinator.gd")
const FixtureDir := "res://src/test/fixtures/saves/"
const UserFiles := ["settings.json", "campaign.json", "shift_results.json", "shift_state.json"]
func _save_instance() -> Object:
var save_script: GDScript = load("res://src/autoload/save.gd")
return save_script.new()
func _fixture(name: String) -> String:
return FileAccess.get_file_as_string(FixtureDir + name)
func _user_path(name: String) -> String:
return ProjectSettings.globalize_path("user://" + name)
func _write_user(name: String, text: String) -> void:
var file := FileAccess.open("user://" + name, FileAccess.WRITE)
if file == null:
return
file.store_string(text)
file.close()
func _read_user(name: String) -> String:
var path := "user://" + name
if not FileAccess.file_exists(path):
return ""
return FileAccess.get_file_as_string(path)
func _remove_user(name: String) -> void:
var path := _user_path(name)
if FileAccess.file_exists("user://" + name) or DirAccess.dir_exists_absolute(path):
DirAccess.remove_absolute(path)
func _snapshot_files() -> Dictionary:
var snapshot := {}
for name in UserFiles:
snapshot[name] = _read_user(name)
return snapshot
func _restore_files(snapshot: Dictionary) -> void:
for name in UserFiles:
_remove_user(name)
_remove_user(name + ".tmp")
var text := str(snapshot.get(name, ""))
if not text.is_empty():
_write_user(name, text)
func test_settings_0_8_fixture_fills_new_defaults() -> bool:
var backup := _snapshot_files()
_write_user("settings.json", _fixture("settings_0_8.json"))
var save := _save_instance()
var loaded: Dictionary = save.load_settings()
var a := TestUtils.check(is_equal_approx(float(loaded.get("look_sensitivity", 0.0)), 0.0031),
"old sensitivity survives")
var b := TestUtils.check(is_equal_approx(float(loaded.get("hud_font_scale", 0.0)), 1.15),
"old font scale survives")
var c := TestUtils.check_eq(bool(loaded.get("invert_y", false)), true, "old invert setting survives")
var d := TestUtils.check_eq(bool(loaded.get("head_bob", true)), false, "old head-bob setting survives")
var e := TestUtils.check_eq(str(loaded.get("ai_difficulty", "")), "hard", "old difficulty survives")
var f := TestUtils.check_eq(bool(loaded.get("tutorial_completed", false)), true,
"old tutorial completion survives")
var g := TestUtils.check(is_equal_approx(float(loaded.get("master_volume", 0.0)), 1.0),
"new master-volume default is filled")
var h := TestUtils.check_eq(bool(loaded.get("captions_enabled", false)), true,
"new caption default is filled")
var i := TestUtils.check_eq(bool(loaded.get("reduce_motion", true)), false,
"new reduced-motion default is filled")
save.free()
_restore_files(backup)
return a and b and c and d and e and f and g and h and i
func test_campaign_0_8_fixture_keeps_progress() -> bool:
var backup := _snapshot_files()
_write_user("campaign.json", _fixture("campaign_0_8.json"))
var save := _save_instance()
var campaign := Campaign.new()
campaign.setup(save.load_campaign())
var a := TestUtils.check_eq(campaign.index, 1, "campaign index survives")
var b := TestUtils.check_eq(campaign.next_venue(), "joke_shop", "next venue survives")
var c := TestUtils.check_eq(campaign.shift_number, 2, "shift number survives")
var d := TestUtils.check_eq(campaign.difficulty, "hard", "difficulty survives")
var e := TestUtils.check_eq(campaign.completed, ["hollywood_building"], "history survives")
var f := TestUtils.check_eq(campaign.last_grade, "B", "last grade survives")
save.free()
_restore_files(backup)
return a and b and c and d and e and f
func test_history_0_8_fixture_loads() -> bool:
var backup := _snapshot_files()
_write_user("shift_results.json", _fixture("shift_results_0_8.json"))
var save := _save_instance()
var results: Array = save.load_shift_results()
var a := TestUtils.check_eq(results.size(), 2, "both old results load")
var b := TestUtils.check_eq(str(results[0].get("scenario_id", "")), "hollywood_building",
"first result keeps its venue")
var c := TestUtils.check_eq(str(results[1].get("scenario_id", "")), "joke_shop",
"second result keeps its venue")
save.free()
_restore_files(backup)
return a and b and c
func test_checkpoint_0_9_0_fixture_restores() -> bool:
var backup := _snapshot_files()
_write_user("shift_state.json", _fixture("shift_state_0_9_0.json"))
var save := _save_instance()
var state: Dictionary = save.load_shift_state()
var a := TestUtils.check_eq(int(state.get("version", 0)), 1, "checkpoint version survives")
var b := TestUtils.check_eq(str(state.get("scenario_id", "")), "joke_shop",
"checkpoint venue survives")
var c := TestUtils.check_eq(bool(state.get("door_locked", false)), true, "door state survives")
var d := TestUtils.check_eq(bool(state.get("flashlight_on", false)), true,
"flashlight state survives")
var e := TestUtils.check_eq(bool(state.get("body_camera_on", false)), true,
"body-camera state survives")
var coordinator := ShiftCoordinator.new()
coordinator.setup(ScenarioFactory.joke_shop(), 900.0, 7)
coordinator.restore(state.get("coordinator", {}))
var f := TestUtils.check(is_equal_approx(coordinator.elapsed_s, 540.0), "elapsed time restores")
var g := TestUtils.check_eq(coordinator.current_phase, 3, "late phase restores")
var h := TestUtils.check(is_equal_approx(coordinator.phase_offset_s, 700.0),
"phase offset restores")
save.free()
_restore_files(backup)
return a and b and c and d and e and f and g and h
func test_checkpoint_round_trip_all_venues() -> bool:
var backup := _snapshot_files()
var venues := [
["hollywood_building", ScenarioFactory.hollywood_building()],
["joke_shop", ScenarioFactory.joke_shop()],
["tv_studio", ScenarioFactory.tv_studio()],
]
var save := _save_instance()
var ok := true
for index in venues.size():
var venue_id: String = venues[index][0]
var scenario: VenueScenario = venues[index][1]
var coordinator := ShiftCoordinator.new()
coordinator.setup(scenario, 900.0, index + 3)
coordinator.elapsed_s = 300.0 + index * 60.0
coordinator.recorder.start_incident("guest", "focus")
var state := {
"version": 1,
"scenario_id": venue_id,
"guard_position": [1.0, 0.1, 8.0 + index],
"guard_yaw": 0.5,
"head_pitch": 0.1,
"door_locked": index % 2 == 0,
"flashlight_on": true,
"body_camera_on": true,
"npc_counter": index + 1,
"coordinator": coordinator.snapshot(),
"npcs": [{"npc_id": "guest_%d" % index, "npc_type": 0}],
}
ok = TestUtils.check_eq(int(save.save_shift_state(state)), OK,
"%s checkpoint writes" % venue_id) and ok
var loaded: Dictionary = save.load_shift_state()
ok = TestUtils.check_eq(str(loaded.get("scenario_id", "")), venue_id,
"%s checkpoint keeps its venue" % venue_id) and ok
ok = TestUtils.check_eq(bool(loaded.get("flashlight_on", false)), true,
"%s checkpoint keeps flashlight state" % venue_id) and ok
ok = TestUtils.check_eq(bool(loaded.get("body_camera_on", false)), true,
"%s checkpoint keeps body-camera state" % venue_id) and ok
ok = TestUtils.check_eq(bool(loaded.get("door_locked", false)), index % 2 == 0,
"%s checkpoint keeps door state" % venue_id) and ok
ok = TestUtils.check_eq((loaded.get("npcs", []) as Array).size(), 1,
"%s checkpoint keeps NPC rows" % venue_id) and ok
var restored := ShiftCoordinator.new()
restored.setup(scenario, 900.0, index + 3)
restored.restore(loaded.get("coordinator", {}))
ok = TestUtils.check(is_equal_approx(restored.elapsed_s, 300.0 + index * 60.0),
"%s checkpoint restores elapsed time" % venue_id) and ok
ok = TestUtils.check(restored.recorder.has_open_incident("guest"),
"%s checkpoint restores an open incident" % venue_id) and ok
save.free()
_restore_files(backup)
return ok
func test_clean_install_has_safe_defaults() -> bool:
var backup := _snapshot_files()
for name in UserFiles:
_remove_user(name)
var save := _save_instance()
var settings: Dictionary = save.load_settings()
var a := TestUtils.check(is_equal_approx(float(settings.get("look_sensitivity", 0.0)), 0.0022),
"clean install gets default sensitivity")
var b := TestUtils.check_eq(str(settings.get("ai_difficulty", "")), "normal",
"clean install gets normal difficulty")
var c := TestUtils.check(save.load_campaign().is_empty(), "clean install has no campaign")
var d := TestUtils.check_eq(save.load_shift_results().size(), 0, "clean install has no history")
var e := TestUtils.check(save.load_shift_state().is_empty(), "clean install has no checkpoint")
save.free()
_restore_files(backup)
return a and b and c and d and e
func test_corrupt_json_falls_back_without_crashing() -> bool:
var backup := _snapshot_files()
_write_user("settings.json", "{not valid json")
_write_user("campaign.json", "{not valid json")
_write_user("shift_results.json", "{not valid json")
_write_user("shift_state.json", "{not valid json")
var save := _save_instance()
var a := TestUtils.check(is_equal_approx(float(save.load_settings().get("look_sensitivity", 0.0)),
0.0022), "corrupt settings fall back to defaults")
var b := TestUtils.check(save.load_campaign().is_empty(), "corrupt campaign returns an empty dictionary")
var c := TestUtils.check_eq(save.load_shift_results().size(), 0, "corrupt history returns an empty array")
var d := TestUtils.check(save.load_shift_state().is_empty(), "corrupt checkpoint returns an empty dictionary")
save.free()
_restore_files(backup)
return a and b and c and d
func test_partial_temp_file_is_ignored() -> bool:
var backup := _snapshot_files()
_remove_user("settings.json")
_write_user("settings.json.tmp", "{\"look_sensitivity\": 0.009, ")
var save := _save_instance()
var loaded: Dictionary = save.load_settings()
var ok := TestUtils.check(is_equal_approx(float(loaded.get("look_sensitivity", 0.0)), 0.0022),
"a leftover partial temp file is not treated as settings")
save.free()
_restore_files(backup)
return ok
func test_failed_atomic_write_keeps_existing_save() -> bool:
var backup := _snapshot_files()
_remove_user("settings.json")
_remove_user("settings.json.tmp")
var save := _save_instance()
var first := TestUtils.check_eq(int(save.save_settings({"look_sensitivity": 0.0022})), OK,
"baseline settings write succeeds")
var temp_path := _user_path("settings.json.tmp")
var made := DirAccess.make_dir_absolute(temp_path)
var err: int = save.save_settings({"look_sensitivity": 0.009})
var a := TestUtils.check(made == OK, "test can block the temp path")
var b := TestUtils.check(err != OK, "blocked temp path reports a write error")
var loaded: Dictionary = save.load_settings()
var c := TestUtils.check(is_equal_approx(float(loaded.get("look_sensitivity", 0.0)), 0.0022),
"a failed replace leaves the valid settings file intact")
DirAccess.remove_absolute(temp_path)
save.free()
_restore_files(backup)
return first and a and b and c
+12
View File
@@ -17,6 +17,12 @@ set -euo pipefail
GODOT="${GODOT:-godot}"
VERSION="$(grep -oP 'config/version="\K[^"]+' project.godot)"
PLAYER_DOCS=(
"CREDITS.md"
"PRIVACY.md"
"docs/accessibility.md"
"docs/known-limitations.md"
)
mkdir -p build
# Presets now embed the PCK into a single binary; drop stale .pck leftovers.
rm -f build/*.pck
@@ -34,6 +40,9 @@ echo "==> Exporting Web (no threads, static-hosting friendly)"
rm -rf build/web
mkdir -p build/web
"$GODOT" --headless --export-release "Web" "build/web/index.html"
for doc in "${PLAYER_DOCS[@]}"; do
cp "$doc" "build/web/$(basename "$doc")"
done
# Ship the web build as a single zip so it can be downloaded and served as-is.
rm -f "build/gig-line-web-${VERSION}.zip"
if command -v zip >/dev/null 2>&1; then
@@ -58,6 +67,9 @@ cp "build/gig-line-linux-${VERSION}.x86_64" "$HQ_DIR/"
cp "build/gig-line-windows-${VERSION}.exe" "$HQ_DIR/"
cp packaging/desktop_hq/override.cfg "$HQ_DIR/"
cp packaging/desktop_hq/README.txt "$HQ_DIR/"
for doc in "${PLAYER_DOCS[@]}"; do
cp "$doc" "$HQ_DIR/$(basename "$doc")"
done
rm -f "build/gig-line-desktop-hq-${VERSION}.zip"
if command -v zip >/dev/null 2>&1; then
(cd "$HQ_DIR" && zip -q -r "../gig-line-desktop-hq-${VERSION}.zip" .)
+84 -4
View File
@@ -78,12 +78,64 @@ for f in "${extras[@]}"; do
fi
done
# Preflight before creating anything on the host: no empty files, and enough
# local disk to assemble/checksum the release. Upload failures are rolled back
# below so a full or interrupted host never keeps a partial release.
total_bytes=0
for f in "${all_artifacts[@]}"; do
if [[ ! -s "$f" ]]; then
echo "error: missing or empty artifact $f — run ./tools/build_releases.sh first" >&2
exit 1
fi
total_bytes=$((total_bytes + $(stat -c '%s' "$f")))
done
free_kb="$(df -Pk build | awk 'NR==2 {print $4}')"
need_kb=$(((total_bytes + 1023) / 1024))
reserve_kb=262144
if [[ -z "${free_kb:-}" || "$free_kb" -lt $((need_kb + reserve_kb)) ]]; then
echo "error: not enough free disk in build/ for ${need_kb} KiB of artifacts plus a 256 MiB reserve (free: ${free_kb:-unknown} KiB)" >&2
exit 1
fi
# Local checksums are useful evidence even though the release host does not need
# a duplicate checksum asset; CI keeps the manifest with the build workspace.
checksum_file="build/SHA256SUMS-$VERSION.txt"
: > "$checksum_file"
for f in "${all_artifacts[@]}"; do
sha256sum "$f" >> "$checksum_file"
done
echo "==> Checksums written to $checksum_file"
cat "$checksum_file"
if [[ "${PUBLISH_DRY_RUN:-0}" == "1" ]]; then
echo "DRY RUN: preflight and checksums passed; no release or upload was created."
exit 0
fi
API="$HOST/api/v1/repos/$REPO/releases"
created_release=0
uploaded_ids=()
rollback_release() {
echo "error: rolling back the partial release upload" >&2
for id in ${uploaded_ids[@]+"${uploaded_ids[@]}"}; do
curl -sS -X DELETE "${AUTH[@]}" "$API/$RELEASE_ID/assets/$id" >/dev/null 2>&1 || true
done
if [[ "$created_release" == "1" ]]; then
curl -sS -X DELETE "${AUTH[@]}" "$API/$RELEASE_ID" >/dev/null 2>&1 || true
echo "removed the new release $TAG" >&2
else
echo "removed only the ${#uploaded_ids[@]} newly uploaded asset(s) from existing release $TAG" >&2
fi
}
echo "==> Creating release $TAG on $HOST/$REPO"
RELEASE_ID="$(curl -s -X POST "$API" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Gig Line $VERSION — playable prototype. See README for controls.\",\"draft\":false,\"prerelease\":false}" \
| python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)"
if [[ -n "$RELEASE_ID" ]]; then
created_release=1
fi
# If the release already exists (tag already pushed), reuse it.
if [[ -z "$RELEASE_ID" ]]; then
@@ -96,10 +148,6 @@ fi
echo "Release id: $RELEASE_ID"
for f in "${all_artifacts[@]}"; do
if [[ ! -f "$f" ]]; then
echo "error: missing artifact $f — run ./tools/build_releases.sh first" >&2
exit 1
fi
echo "==> Uploading $(basename "$f")"
# Fail loudly: a silent 4xx here used to leave a release with no artifacts while
# the job still reported success.
@@ -109,8 +157,16 @@ for f in "${all_artifacts[@]}"; do
echo "error: upload of $(basename "$f") failed with HTTP $code" >&2
cat /tmp/release-upload.json >&2 || true
echo "" >&2
rollback_release
exit 1
fi
asset_id="$(python3 -c "import sys,json;print(json.load(open('/tmp/release-upload.json')).get('id',''))" 2>/dev/null || true)"
if [[ -z "$asset_id" ]]; then
echo "error: upload of $(basename "$f") returned no asset id" >&2
rollback_release
exit 1
fi
uploaded_ids+=("$asset_id")
done
# Verify the release actually carries the artifacts we just sent.
@@ -123,9 +179,33 @@ except Exception:
print(len(d.get('assets', [])))" 2>/dev/null || echo 0)"
if [[ "${have:-0}" -lt "${#all_artifacts[@]}" ]]; then
echo "error: release has $have assets, expected ${#all_artifacts[@]}" >&2
rollback_release
exit 1
fi
echo "Verified $have artifacts on the release"
# Optional retention: set RETAIN_RELEASES=7 to keep the newest seven releases.
# The default 0 disables pruning; never delete old releases implicitly.
RETAIN_RELEASES="${RETAIN_RELEASES:-0}"
if [[ "$RETAIN_RELEASES" =~ ^[0-9]+$ ]] && (( RETAIN_RELEASES > 0 )); then
echo "==> Pruning releases older than the newest $RETAIN_RELEASES"
releases_json="$(curl -sS "${AUTH[@]}" "$API?limit=50")"
while IFS= read -r old_id; do
[[ -z "$old_id" ]] && continue
echo " removing release id $old_id"
curl -sS -X DELETE "${AUTH[@]}" "$API/$old_id" >/dev/null 2>&1 || true
done < <(RELEASES_JSON="$releases_json" RETAIN="$RETAIN_RELEASES" python3 - <<'PYPRUNE'
import json, os
try:
data = json.loads(os.environ["RELEASES_JSON"])
except Exception:
raise SystemExit
keep = int(os.environ["RETAIN"])
for release in sorted(data, key=lambda r: r.get("created_at", ""), reverse=True)[keep:]:
print(release.get("id", ""))
PYPRUNE
)
fi
echo ""
echo "Release live: $HOST/$REPO/releases/tag/$TAG"