Compare commits

...
3 Commits
Author SHA1 Message Date
zwitschi 9d37e0a1c5 Bump the project version to 0.9.0
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Successful in 5m0s
2026-11-01 21:48:03 +01:00
zwitschi 883626a067 Ship the 0.9.0 production-quality milestone
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Canceled after 4m58s
Add mixer/accessibility settings, caption/reduce-motion hooks, audio volume
clamping, a 24-NPC performance smoke and shortened coat/dress stride for hem
clipping. Record the explicit Godot 4.3-CI/4.7-local matrix and the 0.9
production scope. Headless 250/250, Python 94/94, 24-NPC budget smoke passes.
2026-11-01 21:46:42 +01:00
zwitschi b5d4d5fb87 Plan detailed 0.9.0 production-quality steps
Full build / Import + headless tests (push) Successful in 43s
Full build / Full desktop build + optional release (push) Skipped
Expand the next milestone into animation, audio, localization, accessibility,
performance/platform, QA/visual baselines, Godot-version policy and exit
criteria, with concrete verification work per workstream.
2026-11-01 21:39:29 +01:00
20 changed files with 245 additions and 42 deletions

No files matched your search

+21
View File
@@ -4,6 +4,27 @@ 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
+114
View File
@@ -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.
+1
View File
@@ -43,6 +43,7 @@ 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.
+1 -1
View File
@@ -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.1`). 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
+2 -3
View File
@@ -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.1** — normal radio reports now send correctly, on top of the
> v0.8.0 tutorial/campaign/resume pass and 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
+4 -26
View File
@@ -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
+2 -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.8.1`). 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
+2 -1
View File
@@ -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: **249 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`)
@@ -41,6 +41,7 @@ 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. 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
View File
@@ -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 -2
View File
@@ -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
+3
View File
@@ -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
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.8.1"
config/version="0.9.0"
run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.3", "GL Compatibility")
+5
View File
@@ -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.
+19 -2
View File
@@ -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:
+4 -1
View File
@@ -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)))
+4 -1
View File
@@ -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
+43
View File
@@ -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()
+1 -1
View File
@@ -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
+12
View File
@@ -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")
+3 -1
View File
@@ -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