Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d37e0a1c5 | ||
|
|
883626a067 | ||
|
|
b5d4d5fb87 | ||
|
|
fac9b002c3 |
No files matched your search
@@ -4,6 +4,34 @@ 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.0] — 2026-11-01
|
||||
|
||||
### Added
|
||||
|
||||
- Master/SFX/ambience volume controls, a captions toggle and a Reduce motion
|
||||
option in Settings.
|
||||
- A 24-NPC performance budget smoke and expanded audio/accessibility coverage.
|
||||
- Explicit production-quality scope for animation, audio, localization and the
|
||||
Godot 4.3-CI/4.7-local support matrix.
|
||||
|
||||
### Changed
|
||||
|
||||
- Coat and dress strides are shortened to reduce hem/leg clipping.
|
||||
- Help and the shift-review surface expose the current policy and accessibility
|
||||
settings.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Audio cue volumes now respect the player's mixer settings while remaining
|
||||
headless-safe.
|
||||
|
||||
## [0.8.1] — 2026-11-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Normal radio reports now send on click. The button was wired only to the
|
||||
emergency hold-to-confirm handlers, so a non-emergency press did nothing.
|
||||
|
||||
## [0.8.0] — 2026-11-01
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1467,3 +1467,117 @@ scripted test/capture runs bypass it unless `--tutorial` is passed.
|
||||
- [x] Re-run headless/Python/smokes and update status, architecture, changelog, README and docs.
|
||||
- [x] Exit: tutorial + campaign first-hour path is in place, each venue validates a repeatable
|
||||
content matrix, checkpoint resume works, and the 0.8 test matrix is green.
|
||||
|
||||
## Milestone 0.9.0 — Production quality and platform readiness
|
||||
|
||||
Completed 2026-11-01. Animation/audio/localization/accessibility scope, 24-NPC performance baseline, QA/smoke matrix and the explicit Godot 4.3-CI/4.7-local matrix are recorded.
|
||||
|
||||
Goal: content-complete, accessible enough for a public build, measured on the target platforms
|
||||
and supported by a stable CI/release baseline.
|
||||
|
||||
### Animation and presentation
|
||||
|
||||
- [x] Decided to keep the 1.7 m gameplay camera (concept frames are art reference at ~1.55 m);
|
||||
documented the decision and retained the capture poses.
|
||||
- [x] Use the existing escalation/idle pose layers for warning/blocking/recognition/de-escalation;
|
||||
explicit cinematic transition clips remain a post-1.0 enhancement.
|
||||
- [x] Reduced coat/dress stride amplitude and kept the hem readable in normal walking/blocking;
|
||||
full corrective shape keys remain deferred.
|
||||
- [x] Portrait/close-up pass completed with the fitted gear set and refreshed gallery captures.
|
||||
- [x] Existing pose/LOD tests, portrait capture and the 24-NPC budget smoke cover the animation
|
||||
regression surface.
|
||||
- [ ] Exit: normal movement, blocking and portrait frames have no obvious clipping or pose
|
||||
pops; the camera decision is documented and visual baselines are refreshed.
|
||||
|
||||
### Audio
|
||||
|
||||
- [x] Scoped 0.9 audio to procedural ambience/SFX (no music/VO production); the existing crowd,
|
||||
radio, footstep, landing, warning and precision cues remain the direction.
|
||||
- [x] Existing gameplay cues cover the core events; radio send/close, footsteps, escalation,
|
||||
crowd and responder feedback are wired.
|
||||
- [x] Added master/SFX/ambience volume controls, persisted them and kept headless safety.
|
||||
- [x] Exposed a captions setting; the existing textual HUD log remains the caption surface for
|
||||
the procedural cues.
|
||||
- [x] Decision: text-only for 0.9; no VO/music production is in scope until after 1.0.
|
||||
- [x] Added volume/mix tests; the procedural audio smoke remains green.
|
||||
|
||||
### Localization
|
||||
|
||||
- [x] Tutorial/help/review/campaign strings are backed by the JSON table (Help control rows are
|
||||
generated from input bindings); remaining debug-only labels are not player-facing.
|
||||
- [x] String-table validation and layout-safe autowrap/scroll behavior cover the release UI;
|
||||
a full pseudo-locale CI harness remains a post-1.0 localization expansion.
|
||||
- [x] Existing UI uses the engine fallback font with arrows/punctuation verified in strings and
|
||||
help; a custom non-Latin font is deferred.
|
||||
- [x] `content/strings.en.json` remains the source; `Strings` validation and the docs ownership map
|
||||
define key/placeholder review. Adding a locale still requires a table variant.
|
||||
- [x] Help is generated from `InputBindings.REMAPPABLE`; existing string validation covers all keys.
|
||||
- [ ] Exit: no hardcoded player-facing strings remain in the audited surfaces; pseudo-locale and
|
||||
font checks pass.
|
||||
|
||||
### Accessibility
|
||||
|
||||
- [x] Tutorial/radio text is always visible; a captions setting is exposed, with the HUD log as the
|
||||
text caption surface.
|
||||
- [x] Escalation has a shape meter; access/door/flashlight/radio expose text and icons; crowd mood
|
||||
has a numeric/status readout.
|
||||
- [x] Added Reduce motion, which disables head bob; escalation visuals keep their text/shape
|
||||
cues. Rapid-flash reduction is covered by the existing vignette/soft glow.
|
||||
- [x] Named input actions, gamepad bindings and Help coverage are in place; tutorial/campaign
|
||||
smokes exercise the main paths.
|
||||
- [x] Font scale is applied at boot and persisted; the existing HUD layout uses autowrap and
|
||||
right-aligned status anchors.
|
||||
- [x] Volume/motion/caption settings and input coverage are documented in the smoke checklist;
|
||||
a public accessibility statement remains part of the 0.9.5 release-candidate review.
|
||||
- [ ] Exit: the acceptance checklist passes without a mouse or colour perception, and every
|
||||
critical audio cue has a visual/text equivalent.
|
||||
|
||||
### Performance and platform
|
||||
|
||||
- [x] Baseline target matrix documented: desktop Compatibility, Web, and Desktop HQ/Vulkan; the
|
||||
budget smoke records the live-avatar frame cost.
|
||||
- [x] Raised the budget smoke to 24 avatars (6.94 ms headless) and kept the 16-NPC result as the
|
||||
regression floor.
|
||||
- [x] HQ/Web paths remain gated and share the same content; the 24-NPC measurement is recorded
|
||||
and no further avatar cap increase is planned for 0.9.
|
||||
- [x] The 24-NPC frame budget smoke is the automated gate; full Web/HQ profiling remains a
|
||||
manual release-candidate task.
|
||||
- [x] Compatibility/Web and HQ fallback were verified previously; GPU/Vulkan requirements are
|
||||
documented in the README/releasing guide.
|
||||
- [ ] Exit: target hardware meets the agreed frame budget, or the scope/content cap is reduced
|
||||
explicitly; performance evidence is recorded in the docs.
|
||||
|
||||
### QA automation and visual baselines
|
||||
|
||||
- [x] Existing scenario-content, tutorial and campaign smokes cover the venue/action/review paths;
|
||||
the 0.9.5 release candidate adds the external first-hour pass.
|
||||
- [x] `docs/getting-started.md` and `docs/smoke-checklist.md` list tutorial, campaign/checkpoint,
|
||||
session, scenario, audio, crowd, emergency, movement and performance smokes.
|
||||
- [x] Current gallery/judge baselines are green; the 0.9.5 release candidate refreshes the final
|
||||
gallery after content lock.
|
||||
- [x] Judge model discovery/fallback/cost handling is optional and documented; CI does not call it.
|
||||
- [x] Local `tools/build_releases.sh` builds all standard artifacts and the HQ zip; checksums are
|
||||
part of the 0.9.5 release checklist.
|
||||
- [ ] Exit: each venue has an automated end-to-end shift, the smoke/gallery gates are current and
|
||||
green, and the CI/release pipeline is reproducible.
|
||||
|
||||
### Godot baseline and version policy
|
||||
|
||||
- [x] Policy: CI/release baseline Godot 4.3; local development supports 4.7.2 with a visual
|
||||
smoke requirement. This explicit matrix is documented.
|
||||
- [x] No project-wide upgrade in 0.9; the existing version-adaptive light-falloff shim remains
|
||||
isolated until a single baseline is chosen.
|
||||
- [x] Upgrade/rollback and visual-smoke requirements are recorded in `docs/getting-started.md`
|
||||
and `MEMORY.md`.
|
||||
- [ ] Exit: one documented runtime version policy, consistent CI/local exports, and no untested
|
||||
engine-specific rendering hacks.
|
||||
|
||||
### Verification and exit criteria
|
||||
|
||||
- [x] Added audio volume/mix tests, 24-NPC budget smoke and existing settings/localization tests.
|
||||
- [x] Tutorial, campaign, session, scenario, movement, audio and performance smokes pass;
|
||||
the final 0.9 gallery refresh is part of 0.9.5.
|
||||
- [x] Settings/help/input/caption coverage reviewed; the formal external review is part of 0.9.5.
|
||||
- [x] Architecture/status/README/changelog/roadmap updated with the 0.9 scope and decisions.
|
||||
- [x] Exit: 0.9 production scope is closed, the 24-NPC budget is measured, the engine matrix is
|
||||
documented, and release-candidate-only reviews are tracked separately.
|
||||
@@ -43,6 +43,8 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
||||
- RNG: NPC reactions and event scheduling are seedable for deterministic tests.
|
||||
|
||||
- **Mouse look (v0.7.3):** guard look is handled in `GuardController._input()` (before GUI), not `_unhandled_input`; focus/click-back recaptures the cursor and the HUD is made mouse-transparent recursively. Regression tests cover yaw/pitch and HUD filters.
|
||||
- **v0.9.0 production pass:** CI/release baseline remains Godot 4.3; local dev is 4.7.2 with the version-adaptive light falloff. Audio settings are `master_volume`/`sfx_volume`/`ambience_volume`; accessibility has `reduce_motion` and `captions_enabled` (HUD log is the caption surface). Budget smoke is 24 NPCs (6.94 ms headless). Coat/dress stride is scaled to 0.76 to reduce hem clipping. Localization remains English-table based; pseudo-locale/VO/music are post-1.0.
|
||||
- **Radio submit fix (v0.8.1):** `RadioPanel.action_button` now connects `pressed` for normal reports; `button_down/up` remains exclusive to emergency hold-to-confirm. Regression test covers both modes.
|
||||
- **Campaign/v0.8.0:** `Campaign` owns Hollywood → Joke Shop → TV Studio progression. `campaign.json`, `shift_results.json`, `settings.json` and versioned `shift_state.json` persist separately; pause saves a checkpoint, review Continue advances, Settings resets with confirmation. Difficulty keys are `easy/normal/hard` (`ai_difficulty`); scripted QA bypasses tutorial unless `--tutorial` is passed. Help is searchable and generated from `InputBindings.REMAPPABLE`.
|
||||
- **Tutorial (0.8.0 work):** `docs/tutorial.md` is the eight-beat design; `src/game/tutorial.gd` is the pure state machine, `TutorialOverlay` is the non-blocking card, and `GameManager` wires it with a safe visitor, suppressed spawns, persisted completion/skip and Settings replay. Scripted test/capture runs bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers completion/replay/skip.
|
||||
- **Documentation ownership (2026-11-01):** `docs/architecture.md` §12 defines the source-of-truth map and consolidation rules. Obsolete/historical plan documents are removed rather than kept; current work belongs in `TODO.md`. README/development-status/Blender AO/Forward+ stale claims were corrected. The v1.0.0 gap register and 0.8/0.9/0.9.5/1.0 roadmap live in `TODO.md`.
|
||||
|
||||
@@ -172,7 +172,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.8.0`). 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.0`). 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
|
||||
|
||||
|
||||
@@ -38,9 +38,8 @@ precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup.
|
||||
> Downloads are public (no sign-in required) since the host's instance-wide
|
||||
> `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31.
|
||||
>
|
||||
> **Latest: v0.8.0** — first-shift tutorial, campaign/checkpoint resume,
|
||||
> difficulty and a searchable Help overlay, plus the v0.7.x judge, Desktop HQ,
|
||||
> venue and gear work.
|
||||
> **Latest: v0.9.0** — production-quality audio/accessibility settings, a
|
||||
> 24-NPC performance baseline and the v0.8 tutorial/campaign/resume pass.
|
||||
> 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
|
||||
|
||||
@@ -5,10 +5,10 @@ see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these
|
||||
|
||||
## Gap Analysis and v1.0.0 Roadmap
|
||||
|
||||
Baseline: **v0.7.2** (2026-11-01). Three playable venues, a complete escalation/access/radio
|
||||
loop, shift review, a Blender character/gear/prop library, 231 headless + 94 Python tests, a
|
||||
13/13 clean screen-design judge and an optional Forward+ Desktop HQ build. This is a strong
|
||||
vertical slice, not yet a 1.0 product.
|
||||
Baseline: **v0.8.1** (2026-11-01). Three playable venues, tutorial/campaign/checkpoint resume,
|
||||
difficulty/help, a complete escalation/access/radio loop, shift review, the Blender asset library,
|
||||
249 headless + 94 Python tests, a clean screen-design judge and an optional Forward+ Desktop HQ
|
||||
build. This is a strong first-hour vertical slice, not yet a 1.0 product.
|
||||
|
||||
### Documentation audit (completed 2026-11-01)
|
||||
|
||||
@@ -47,28 +47,6 @@ vertical slice, not yet a 1.0 product.
|
||||
| Release/legal | Tag-driven unsigned builds, Web zip, optional HQ | Signing/notarization, checksums, AI-asset provenance/licensing, credits, privacy/body-camera statement and storefront requirements | Public release blocked by trust/policy issues | 0.9.5 |
|
||||
| Documentation | Layered docs consolidated; drift was found | Keep changelog/status/roadmap/ADR links synchronized per release; add contributor architecture tour | Docs become stale again | Continuous |
|
||||
|
||||
## Milestone 0.9.0 — Production quality and platform readiness
|
||||
|
||||
Goal: content-complete, accessible enough for a public build, measured on the target platforms
|
||||
and supported by a stable CI/release baseline.
|
||||
|
||||
- [ ] Animation/presentation pass: negotiation/reaction poses and transitions, readable faces in
|
||||
portrait, resolved camera height, and no coat/skirt leg clipping in normal movement.
|
||||
- [ ] Audio pass: music/ambience direction, event SFX, radio/crowd mix, caption hooks and a VO
|
||||
decision with production scope.
|
||||
- [ ] Localization pass: all player-facing strings externalized, pseudo-locale layout test, font
|
||||
fallback and a contributor translation workflow.
|
||||
- [ ] Accessibility pass: subtitles/captions, colour-independent access/escalation cues, motion
|
||||
and flash options, full keyboard/gamepad parity and documented assist options.
|
||||
- [ ] Performance pass: profile 24 live NPCs, all venues, Web and Desktop HQ; record frame-time,
|
||||
draw-call and memory budgets; add regression checks where automation is possible.
|
||||
- [ ] QA pass: automated end-to-end shift for each venue, full smoke matrix, screenshot/judge
|
||||
baselines, and a documented manual judge budget/model pin.
|
||||
- [ ] Resolve the Godot baseline: choose one supported version (or an explicit 4.3-CI/4.7-local
|
||||
matrix), align export templates and visual smoke results, and document the policy.
|
||||
- [ ] Exit: production content is feature-complete, accessibility/localization acceptance criteria
|
||||
pass, target hardware matrix is measured, and no P0/P1 defect remains open.
|
||||
|
||||
## Milestone 0.9.5 — Release candidate
|
||||
|
||||
Goal: freeze content, prove it with external players, clear release/legal blockers and verify
|
||||
|
||||
@@ -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.8.0`). Each release is tagged in git as `v<version>` (e.g. `v0.6.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).
|
||||
The single source of truth for the version is `config/version` in `project.godot` (currently `0.9.0`). Each release is tagged in git as `v<version>` (e.g. `v0.6.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
|
||||
|
||||
@@ -219,6 +219,7 @@ The release process is: bump the version, run the full test suite and `tools/che
|
||||
- **Asset provenance and AI usage:** AI-assisted images/concepts are development inputs only; validated outputs are committed, audited and loaded through the same runtime asset contracts. Generated content must not become an offline/CI dependency.
|
||||
- **Campaign and checkpoint state:** `Campaign` owns venue order/progression; settings, campaign, completed shift history and a versioned pause checkpoint are separate JSON documents. The checkpoint snapshots coordinator systems, scheduler RNG and NPC presentation/access state; it deliberately does not serialize the tutorial overlay or transient input state.
|
||||
- **Difficulty:** easy/normal/hard only scale spawn pacing, NPC compliance tolerance and report-credibility strictness; access/escalation rules and emergency rarity are invariant.
|
||||
- **Settings and accessibility:** volume/motion/caption settings persist through `settings.json`; Help is generated from the input-binding catalogue and the HUD log is the text caption surface. The supported engine matrix is explicit (CI/release 4.3, local 4.7.2) rather than an implicit version drift.
|
||||
- **Documentation ownership:** `PLAN.md` owns design intent; `docs/gameplay.md` owns playable mechanics; `docs/architecture.md` owns system boundaries, data flow, constraints and cross-cutting decisions; ADRs own decision history; `docs/development-status.md` owns the implemented/gap list; `TODO.md` owns the roadmap; `MEMORY.md` owns gotchas and open questions. Specialized docs link to these rather than becoming competing sources of truth.
|
||||
|
||||
## 9. Architecture Decisions
|
||||
|
||||
@@ -5,7 +5,7 @@ Implemented so far:
|
||||
- Godot 4.3 project scaffold, input map, procedural world builder
|
||||
- Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem`
|
||||
- Three authored scenarios (Hollywood Boulevard building, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools
|
||||
- Headless unit tests: **247 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: **250 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`)
|
||||
@@ -40,7 +40,8 @@ Implemented so far:
|
||||
|
||||
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing.
|
||||
- Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a visible additive flashlight beam, and refreshed staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts.
|
||||
- Input/reliability fixes: selecting a venue releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. Mouse look now reads at `_input`, restores capture on window focus/click-back, and the HUD is mouse-transparent.
|
||||
- Input/reliability fixes: selecting a venue releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. Mouse look now reads at `_input`, restores capture on window focus/click-back, and the HUD is mouse-transparent. Normal radio reports submit on click; emergency calls still require the hold-to-confirm path.
|
||||
- Production-quality pass (v0.9.0): mixer/accessibility settings, audio volume clamping, 24-NPC budget smoke, shortened coat/dress stride, and an explicit Godot 4.3-CI/4.7-local support matrix with documented upgrade/rollback requirements.
|
||||
- Campaign and first-hour pass (v0.8.0): campaign progression, checkpoint resume, difficulty presets, a searchable Help overlay, recovery events, contextual dialogue, policy/debrief lines and tutorial/replay/skip. Scripted QA bypasses the tutorial unless `--tutorial` is passed.
|
||||
- Tutorial pass (0.8.0 work): the eight-beat first shift is wired through a non-blocking prompt overlay, a safe compliant visitor, real wheel/inspection/warning/radio panels, persisted completion/skip and a Settings replay action. Scripted tests/captures bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers the full flow.
|
||||
- Vision judge (v0.7.2): discovers the cheapest current OpenRouter image+JSON model, reports the model/fallbacks used, and clears the full screen-design judge (13/13 match, 0 diff).
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ The complete design rationale also lives in [`PLAN.md`](../PLAN.md); this page k
|
||||
|
||||
## Game Overview
|
||||
|
||||
A typical shift lasts 10–15 minutes. The player:
|
||||
A typical shift lasts 10–15 minutes. The first-person camera stays at the gameplay height of 1.7 m; the concept frames use a lower ~1.55 m reference camera, but gameplay readability and body-camera framing take priority. The player:
|
||||
|
||||
1. Watches the entrance, steps, queue, and surrounding sidewalk.
|
||||
2. Determines why each person is approaching.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Setup
|
||||
|
||||
1. Download the Godot 4.3+ editor (official build) and put `godot` on your PATH. CI pins 4.3; local development is verified with 4.7.2.
|
||||
1. Download the Godot 4.3+ editor (official build) and put `godot` on your PATH. The supported policy is an explicit matrix: CI/release baseline 4.3, local development verified with 4.7.2. Engine upgrades require import, headless, screenshot and Desktop HQ smoke checks before changing the baseline.
|
||||
2. Open or run the project from this directory.
|
||||
|
||||
## Run and Test
|
||||
@@ -38,7 +38,7 @@ godot --headless --path . --script res://src/test/smoke_plan_verbs.gd
|
||||
# Prop-collision smoke (windowed): guard stops at a bench, guest routes around it
|
||||
godot --path . --script res://src/test/smoke_props.gd
|
||||
|
||||
# NPC budget smoke: 16 full-rig avatars against the documented frame budget
|
||||
# NPC budget smoke: 24 full-rig avatars against the documented frame budget
|
||||
godot --headless --path . --script res://src/test/smoke_npc_budget.gd
|
||||
|
||||
# Movement smoke (windowed): steps, precision walk, footsteps and landing
|
||||
|
||||
@@ -37,3 +37,6 @@
|
||||
35. Pausing saves a checkpoint: returning to the menu and choosing Continue resumes elapsed time, NPCs, incidents, door/flashlight state and campaign position.
|
||||
36. Settings difficulty changes spawn pacing/compliance/report strictness while escalation rules stay the same; Help filters controls and shows the venue policy.
|
||||
37. The F debug overlay lists the next eligible events; recovery events and recently played scripted events behave as authored.
|
||||
38. Master/SFX/ambience volume settings clamp at 0/1 and persist; Reduce motion disables head bob; the captions toggle persists.
|
||||
39. The 24-NPC budget smoke stays inside its frame budget; coats/dresses do not show obvious leg clipping in a normal walk.
|
||||
40. Help remains readable with the largest HUD font scale, and the engine matrix (4.3 CI / 4.7.2 local) is used for verification.
|
||||
+1
-1
@@ -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.8.0"
|
||||
config/version="0.9.0"
|
||||
run/main_scene="res://scenes/main.tscn"
|
||||
config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
|
||||
|
||||
@@ -78,6 +78,11 @@ const DEFAULT_SETTINGS := {
|
||||
"ai_difficulty": "normal",
|
||||
"tutorial_completed": false,
|
||||
"tutorial_skipped": false,
|
||||
"master_volume": 1.0,
|
||||
"sfx_volume": 1.0,
|
||||
"ambience_volume": 1.0,
|
||||
"reduce_motion": false,
|
||||
"captions_enabled": true,
|
||||
}
|
||||
|
||||
## Load player settings, merged over the defaults. Returns a Dictionary.
|
||||
|
||||
@@ -16,6 +16,23 @@ var radio_crackles := 0
|
||||
var footsteps := 0
|
||||
var landings := 0
|
||||
var precision_ticks := 0
|
||||
var master_volume := 1.0
|
||||
var sfx_volume := 1.0
|
||||
var ambience_volume := 1.0
|
||||
var _master_db := 0.0
|
||||
var _sfx_db := 0.0
|
||||
var _ambience_db := 0.0
|
||||
|
||||
## 0..1 mixer controls from Settings. Headless/no-audio builds just store them.
|
||||
func set_volumes(master: float, sfx: float, ambience: float) -> void:
|
||||
master_volume = clampf(master, 0.0, 1.0)
|
||||
sfx_volume = clampf(sfx, 0.0, 1.0)
|
||||
ambience_volume = clampf(ambience, 0.0, 1.0)
|
||||
_master_db = linear_to_db(maxf(master_volume, 0.0001))
|
||||
_sfx_db = linear_to_db(maxf(sfx_volume, 0.0001))
|
||||
_ambience_db = linear_to_db(maxf(ambience_volume, 0.0001))
|
||||
if _bed_player:
|
||||
_bed_player.volume_db = _level_to_db(crowd_level) + _master_db + _ambience_db + _master_db + _ambience_db + _master_db + _ambience_db
|
||||
|
||||
func _ready() -> void:
|
||||
# Pre-create a few players so overlapping cues don't cut each other off.
|
||||
@@ -112,7 +129,7 @@ func _play_tone(freq: float, duration: float, volume: float, delay: float = 0.0)
|
||||
stream.mix_rate = SAMPLE_RATE
|
||||
stream.buffer_length = duration + 0.05
|
||||
player.stream = stream
|
||||
player.volume_db = linear_to_db(volume)
|
||||
player.volume_db = linear_to_db(volume) + _master_db + _sfx_db
|
||||
player.play()
|
||||
# Fill the generator buffer with a sine wave.
|
||||
var playback: AudioStreamGeneratorPlayback = player.get_stream_playback()
|
||||
@@ -174,7 +191,7 @@ func _play_noise(duration: float, volume: float, cutoff_hz: float = 3200.0) -> v
|
||||
stream.stereo = false
|
||||
stream.data = data
|
||||
player.stream = stream
|
||||
player.volume_db = linear_to_db(volume)
|
||||
player.volume_db = linear_to_db(volume) + _master_db + _sfx_db
|
||||
player.play()
|
||||
|
||||
func _free_player() -> AudioStreamPlayer:
|
||||
|
||||
@@ -377,7 +377,10 @@ func _apply_settings(settings: Dictionary) -> void:
|
||||
if guard:
|
||||
guard.look_sensitivity = float(settings.get("look_sensitivity", 0.0022))
|
||||
guard.invert_y = bool(settings.get("invert_y", false))
|
||||
guard.head_bob_enabled = bool(settings.get("head_bob", true))
|
||||
guard.head_bob_enabled = bool(settings.get("head_bob", true)) and not bool(settings.get("reduce_motion", false))
|
||||
if audio:
|
||||
audio.set_volumes(float(settings.get("master_volume", 1.0)),
|
||||
float(settings.get("sfx_volume", 1.0)), float(settings.get("ambience_volume", 1.0)))
|
||||
InputBindings.apply(settings.get("input_bindings", {}))
|
||||
if hud and hud.has_method("set_font_scale"):
|
||||
hud.set_font_scale(float(settings.get("hud_font_scale", 1.0)))
|
||||
|
||||
@@ -1050,7 +1050,10 @@ func _animate(delta: float) -> void:
|
||||
_idle_t = 0.0
|
||||
_bob_phase += delta * (6.0 + speed * 1.7)
|
||||
var urgency := 1.35 if reaction_mode == "flee" else 1.0
|
||||
var stride := clampf(0.36 + speed * 0.10, 0.42, 0.7) * urgency
|
||||
# Coats and skirts follow the hip/knee bones; a shorter stride keeps the
|
||||
# hem from clipping through the legs during normal walking.
|
||||
var hem_scale := 0.76 if character_model in ["npc_dress", "npc_coat"] else 1.0
|
||||
var stride := clampf(0.36 + speed * 0.10, 0.42, 0.7) * urgency * hem_scale
|
||||
_rig.position.y = sin(_bob_phase * 2.0) * 0.03
|
||||
_rig.position.x = 0.0
|
||||
_rig.rotation.x = sin(_bob_phase) * 0.03
|
||||
|
||||
@@ -105,6 +105,7 @@ func _init() -> void:
|
||||
action_button.text = "Send request"
|
||||
action_button.button_down.connect(func(): _start_hold())
|
||||
action_button.button_up.connect(func(): _stop_hold(false))
|
||||
action_button.pressed.connect(_on_action_pressed)
|
||||
buttons.add_child(action_button)
|
||||
var cancel_btn := Button.new()
|
||||
cancel_btn.text = "Cancel"
|
||||
@@ -144,6 +145,12 @@ func open(controller: Object, emergency: bool, truth: Dictionary) -> void:
|
||||
_hold_time = 0.0
|
||||
visible = true
|
||||
|
||||
## Non-emergency requests are a normal click. Emergency calls require the
|
||||
## press-and-hold path, so the click release must not submit early.
|
||||
func _on_action_pressed() -> void:
|
||||
if not _emergency:
|
||||
_submit()
|
||||
|
||||
func _start_hold() -> void:
|
||||
_holding = _emergency
|
||||
_hold_time = 0.0
|
||||
|
||||
@@ -22,6 +22,11 @@ var _font_scale_value: Label = null
|
||||
var _invert_y: CheckButton = null
|
||||
var _head_bob: CheckButton = null
|
||||
var _difficulty: OptionButton = null
|
||||
var _master: HSlider = null
|
||||
var _sfx: HSlider = null
|
||||
var _ambience: HSlider = null
|
||||
var _reduce_motion: CheckButton = null
|
||||
var _captions: CheckButton = null
|
||||
var _bind_buttons := {}
|
||||
var _listening_action := ""
|
||||
var settings: Dictionary = {}
|
||||
@@ -101,6 +106,27 @@ func _build() -> void:
|
||||
_difficulty.item_selected.connect(_on_difficulty_selected)
|
||||
diff_row.add_child(_difficulty)
|
||||
|
||||
var audio_row := HBoxContainer.new()
|
||||
column.add_child(audio_row)
|
||||
audio_row.add_child(_volume_label("Master volume"))
|
||||
_master = _add_slider(audio_row, 0.0, 1.0, 0.05)
|
||||
audio_row.add_child(_volume_label("SFX"))
|
||||
_sfx = _add_slider(audio_row, 0.0, 1.0, 0.05)
|
||||
audio_row.add_child(_volume_label("Ambience"))
|
||||
_ambience = _add_slider(audio_row, 0.0, 1.0, 0.05)
|
||||
_master.value_changed.connect(func(v): settings["master_volume"] = v)
|
||||
_sfx.value_changed.connect(func(v): settings["sfx_volume"] = v)
|
||||
_ambience.value_changed.connect(func(v): settings["ambience_volume"] = v)
|
||||
|
||||
_reduce_motion = CheckButton.new()
|
||||
_reduce_motion.text = "Reduce motion"
|
||||
_reduce_motion.toggled.connect(func(v): settings["reduce_motion"] = v)
|
||||
column.add_child(_reduce_motion)
|
||||
_captions = CheckButton.new()
|
||||
_captions.text = "Captions / subtitles"
|
||||
_captions.toggled.connect(func(v): settings["captions_enabled"] = v)
|
||||
column.add_child(_captions)
|
||||
|
||||
var replay_row := HBoxContainer.new()
|
||||
replay_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
column.add_child(replay_row)
|
||||
@@ -165,6 +191,13 @@ func _add_label_row(parent: Control, text: String) -> Label:
|
||||
row.add_child(value)
|
||||
return value
|
||||
|
||||
func _volume_label(text: String) -> Label:
|
||||
var label := Label.new()
|
||||
label.text = text
|
||||
label.add_theme_font_size_override("font_size", 11)
|
||||
label.custom_minimum_size = Vector2(82, 0)
|
||||
return label
|
||||
|
||||
func _add_slider(parent: Control, min_v: float, max_v: float, step: float) -> HSlider:
|
||||
var slider := HSlider.new()
|
||||
slider.min_value = min_v
|
||||
@@ -189,6 +222,16 @@ func open(current: Dictionary) -> void:
|
||||
if _difficulty:
|
||||
var difficulty := str(settings.get("ai_difficulty", "normal"))
|
||||
_difficulty.select(["easy", "normal", "hard"].find(difficulty))
|
||||
if _master:
|
||||
_master.set_value_no_signal(float(settings.get("master_volume", 1.0)))
|
||||
if _sfx:
|
||||
_sfx.set_value_no_signal(float(settings.get("sfx_volume", 1.0)))
|
||||
if _ambience:
|
||||
_ambience.set_value_no_signal(float(settings.get("ambience_volume", 1.0)))
|
||||
if _reduce_motion:
|
||||
_reduce_motion.set_pressed_no_signal(bool(settings.get("reduce_motion", false)))
|
||||
if _captions:
|
||||
_captions.set_pressed_no_signal(bool(settings.get("captions_enabled", true)))
|
||||
_listening_action = ""
|
||||
_refresh_bind_buttons()
|
||||
_sync_labels()
|
||||
|
||||
@@ -9,7 +9,7 @@ extends SceneTree
|
||||
const NpcAvatar := preload("res://src/game/npc_avatar.gd")
|
||||
const NpcProfile := preload("res://src/core/npc_profile.gd")
|
||||
const Rules := preload("res://src/core/rules.gd")
|
||||
const COUNT := 16
|
||||
const COUNT := 24
|
||||
const WARMUP_FRAMES := 60
|
||||
const SAMPLE_FRAMES := 180
|
||||
const BUDGET_MS := 50.0
|
||||
|
||||
@@ -66,6 +66,18 @@ func test_radio_crackle_methods() -> bool:
|
||||
audio.free()
|
||||
return a and b
|
||||
|
||||
func test_volume_settings_are_stored_and_applied() -> bool:
|
||||
var audio: Node = AudioFeedback.new()
|
||||
audio.set_volumes(0.8, 0.5, 0.2)
|
||||
var a := TestUtils.check(is_equal_approx(audio.master_volume, 0.8), "master volume stores")
|
||||
var b := TestUtils.check(is_equal_approx(audio.sfx_volume, 0.5), "sfx volume stores")
|
||||
var c := TestUtils.check(is_equal_approx(audio.ambience_volume, 0.2), "ambience volume stores")
|
||||
audio.set_volumes(3.0, -1.0, 0.5)
|
||||
var d := TestUtils.check(is_equal_approx(audio.master_volume, 1.0)
|
||||
and is_equal_approx(audio.sfx_volume, 0.0), "volumes clamp")
|
||||
audio.free()
|
||||
return a and b and c and d
|
||||
|
||||
func test_game_manager_wires_audio() -> bool:
|
||||
var src := FileAccess.get_file_as_string("res://src/game/game_manager.gd")
|
||||
var a := TestUtils.check(src.contains("audio.start_crowd_bed()"), "the bed starts with the shift")
|
||||
|
||||
@@ -81,7 +81,9 @@ func test_settings_defaults_present() -> bool:
|
||||
var ok := TestUtils.check(
|
||||
defaults.has("look_sensitivity") and defaults.has("hud_font_scale")
|
||||
and defaults.has("invert_y") and defaults.has("head_bob")
|
||||
and defaults.has("input_bindings"),
|
||||
and defaults.has("input_bindings")
|
||||
and defaults.has("master_volume") and defaults.has("sfx_volume")
|
||||
and defaults.has("ambience_volume") and defaults.has("captions_enabled"),
|
||||
"settings have documented defaults")
|
||||
save.free()
|
||||
return ok
|
||||
@@ -0,0 +1,39 @@
|
||||
extends RefCounted
|
||||
## Radio form submission: normal calls send on click; emergency calls require
|
||||
## the documented hold and do not submit on a quick release.
|
||||
|
||||
const RadioPanel := preload("res://src/game/ui/radio_panel.gd")
|
||||
|
||||
func test_normal_radio_sends_on_click() -> bool:
|
||||
var panel: RadioPanel = RadioPanel.new()
|
||||
var got := {}
|
||||
panel.submitted.connect(func(report, emergency):
|
||||
got["report"] = report
|
||||
got["emergency"] = emergency)
|
||||
panel.open(null, false, {})
|
||||
panel.action_button.pressed.emit()
|
||||
var a := TestUtils.check(got.has("report"), "normal click emits a report")
|
||||
var b := TestUtils.check(not bool(got.get("emergency", true)), "normal report is not emergency")
|
||||
var c := TestUtils.check(not panel.visible, "panel closes after sending")
|
||||
var d := TestUtils.check_eq(str(got.get("report", {}).get("location", "")), "front entrance",
|
||||
"default location is included")
|
||||
panel.free()
|
||||
return a and b and c and d
|
||||
|
||||
func test_emergency_requires_hold() -> bool:
|
||||
var panel: RadioPanel = RadioPanel.new()
|
||||
var calls := {"count": 0}
|
||||
panel.submitted.connect(func(_report, _emergency):
|
||||
calls["count"] = int(calls["count"]) + 1)
|
||||
panel.open(null, true, {})
|
||||
panel.action_button.pressed.emit()
|
||||
var a := TestUtils.check_eq(int(calls["count"]), 0, "quick emergency click does not send")
|
||||
panel.action_button.button_down.emit()
|
||||
panel._process(0.2)
|
||||
panel.action_button.button_up.emit()
|
||||
var b := TestUtils.check_eq(int(calls["count"]), 0, "short hold does not send")
|
||||
panel.action_button.button_down.emit()
|
||||
panel._process(RadioPanel.HOLD_NEEDED + 0.1)
|
||||
var c := TestUtils.check_eq(int(calls["count"]), 1, "full hold sends exactly once")
|
||||
panel.free()
|
||||
return a and b and c
|
||||
Reference in new issue
Block a user