Compare commits

...
2 Commits
Author SHA1 Message Date
zwitschi 28f2a48811 Add the optional Forward+ desktop HQ build
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Successful in 5m20s
Package a Forward+ preset with volumetric fog, SDFGI and a volumetric flashlight
for a separate Linux/Windows Desktop HQ zip. Gate the effects on a real
RenderingDevice, tune LATE fog, and adapt local light falloff for Godot 4.7.
Record the Compatibility/Forward+ A/B captures and metrics, add the artifact to
the build/publish scripts, and keep the default Compatibility/Web path
unchanged. Headless 231/231, Python 87/87; exported HQ binary boots with Vulkan.
2026-11-01 19:40:42 +01:00
zwitschi 64f0c6a198 Document the local Godot 4.7.2 toolchain
Full build / Full desktop build + optional release (push) Skipped
Full build / Import + headless tests (push) Successful in 38s
Record the upgraded 4.7.2 binary, the retained 4.3 backup and the matching
export templates. Keep the project/CI baseline at 4.3 and note that 4.7.2
verified import, tests, the session smoke and a Linux export. Hide the
4.7-generated per-script UID sidecars from this clone only.
2026-11-01 19:16:55 +01:00
25 changed files with 348 additions and 49 deletions

No files matched your search

+24
View File
@@ -4,6 +4,30 @@ 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.7.1] — 2026-11-01
### Added
- An optional Desktop HQ build for Windows and Linux that uses Forward+,
volumetric fog and SDFGI, packaged with an `override.cfg` beside the
executable.
- Volumetric flashlight haze in the Desktop HQ build, with a LATE-phase preset
tuned so the entrance facade stays readable.
### Changed
- The normal Compatibility build remains the default; the preset only affects
the extracted Desktop HQ copy.
- Night-time light falloff adapts to Godot 4.7 while keeping the 4.3 baseline
unchanged.
### Fixed
- The flashlight no longer disappears at night on Godot 4.7 under the old
attenuation curve.
- Headless and Web builds stay non-volumetric and boot normally when Forward+
is unavailable.
## [0.7.0] — 2026-11-01
### Changed
+32
View File
@@ -1279,3 +1279,35 @@ have no stage in the committed `docs/screenshots/` gallery, only in `capture_rea
0 bad**; the new screenshots are 18.2 / 32.6 MAE against their references.
- [-] Update `docs/architecture.md` (venue geometry), `README.md` (gallery) and the screenshot
table when the stages land.
## Forward+ desktop preset and a volumetric flashlight (v0.7.1)
Completed 2026-11-01. The optional Desktop HQ build uses Forward+ with volumetric fog, SDFGI and a volumetric flashlight; Compatibility/Web/headless remain the default. The entrance A/B improved the metric gap 1.568 → 1.187. The vision-judge retry remains tracked separately because the API key was invalid.
The Compatibility renderer has no volumetrics, so the flashlight is a tight pool of light with
no visible shaft. A second desktop build can render it properly without touching the CI/default
path.
- [x] Write the preset as an `override.cfg` (Godot reads it beside the binary/project) that sets
`rendering/renderer/rendering_method="forward_plus"`, enables `volumetric_fog_*` and SDFGI,
and keeps the Compatibility build as the default (`project.godot` unchanged). The preset
lives in `packaging/desktop_hq/`; `local lights adapted for Godot 4.7 falloff` too.
- [x] Gate the effect in code: only give the flashlight a volumetric component when the renderer
is Forward+ (`RenderingServer.get_rendering_device() != null`), so Web/CI builds are
untouched and the existing headless tests keep passing. Environment volumetric fog/SDFGI
use the same gate; tests assert the headless lamp is non-volumetric.
- [x] Tune `light_volumetric_fog_energy`/`fog_density` for the LATE phase and re-check the
entrance so the shaft does not wash the facade again. LATE density is 0.020 and the lamp
volumetric energy is 3.0; the entrance read improves without bleaching the brick.
- [x] A/B the two renderers with `capture_realism.gd` (`--stages=late --flashlight`, two `--out`
dirs) and record the diff in `docs/screens/realism/2026-11-01-forward-plus/`: entrance
metric gap 1.568 → 1.187, two-shot mean 0.907 → 0.723. Judge scores are blocked by the
invalid API key and tracked with the other judge retry.
- [x] Add the preset to `tools/build_releases.sh` and `docs/RELEASING.md` as an optional
"desktop HQ" artifact, and note the runtime requirement (Vulkan driver) in `README.md`.
`gig-line-desktop-hq-<version>.zip` packages the Linux/Windows binaries, override.cfg and
README; `publish_release.sh` uploads it when present.
- [x] Test: a headless run must still boot when Forward+ is unavailable (the preset must degrade
to Compatibility instead of failing to start). Verified with the preset in the project
root: `godot --headless --path . --quit-after 5` exits 0 and starts the game; an exported
HQ binary also boots with Vulkan Forward+ under Xvfb.
+16
View File
@@ -44,6 +44,22 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
- Shift length is the scenario's `shift_duration_s` (900 s in all three venues). Tests and the screenshot tool override elapsed time; they do not change the default.
- RNG: NPC reactions and event scheduling are seedable for deterministic tests.
- **Desktop HQ preset (v0.7.1):** `packaging/desktop_hq/override.cfg` switches a
copied executable to Forward+; `Materials.forward_plus_active()` gates
volumetric fog/SDFGI and `light_volumetric_fog_energy`, and
`Materials.modern_light_falloff()` compensates for Godot 4.7's much harsher
local-light falloff. LATE volumetric density is 0.020 with flashlight
volumetric energy 3.0. `tools/build_releases.sh` packages the Linux/Windows
binaries + override into `gig-line-desktop-hq-<version>.zip`;
`publish_release.sh` uploads it when present. A/B report:
`docs/screens/realism/2026-11-01-forward-plus/` (entrance gap 1.568→1.187).
- **Local Godot toolchain (2026-11-01):** `~/.local/bin/godot` is 4.7.2 stable;
4.3 stable is kept as `~/.local/bin/godot-4.3.stable`. Export templates for
4.7.2 are installed under `~/.local/share/godot/export_templates/4.7.2.stable`
(4.3 remains). The project and CI still pin 4.3 in `project.godot`/the Gitea
workflow; 4.7.2 imports, runs the 231-test suite, session-shell smoke and a
Linux export successfully. Godot 4.7 writes `*.gd.uid` files; they are kept
out of this clone's status via `.git/info/exclude` until the project upgrades.
- **Blender invocation (v0.6.0):** `flatpak run org.blender.Blender --background --python
/run/media/system/DATA/Documents/06-games/security-game/tools/npc_pipeline.py -- build`.
The flatpak cannot read `/tmp`; use absolute repo paths. `assets/models/npc/.gdignore` is
+2 -2
View File
@@ -168,11 +168,11 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD
- **Persistence:** JSON in `user://` for settings and shift history.
- **Testing:** custom headless runner (`src/test/test_runner.gd`); GUT was evaluated and not adopted. Python tooling tests cover the asset/screenshot pipelines.
- **Source control/CI:** git on Gitea; `.gitea/workflows/build.yml` runs headless checks and builds/publishes releases on tags.
- **Targets:** Windows, Linux, macOS, and Web. Release artifacts include portables, an NSIS installer, an AppImage, and a Web zip.
- **Targets:** Windows, Linux, macOS, and Web. Release artifacts include portables, an NSIS installer, an AppImage, a Web zip, and an optional Desktop HQ zip (Forward+ volumetric lighting; Vulkan required).
## Versioning and Delivery
`config/version` in `project.godot` is the single version source (currently `0.7.0`). Releases are tagged `v<version>`; the CI pipeline builds and publishes the six artifacts. 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.7.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.
## Design Direction
+1 -1
View File
@@ -52,7 +52,7 @@ precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup.
If you are here to develop or test locally instead of downloading, jump to
[Setup and Run](#setup-and-run) below.
The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4.3 + GDScript): a procedural Hollywood Boulevard entrance, a first-person guard, simulated NPCs with intents and reactions, the six-level escalation model, access inspection and admission decisions, radio calls with credibility checks, reputation, crowd mood, and an end-of-shift review.
The project is under active development. The design and architecture are documented, and a playable prototype vertical slice exists (Godot 4 + GDScript): a procedural Hollywood Boulevard entrance, a first-person guard, simulated NPCs with intents and reactions, the six-level escalation model, access inspection and admission decisions, radio calls with credibility checks, reputation, crowd mood, and an end-of-shift review.
## Game Overview
+3 -23
View File
@@ -3,31 +3,11 @@
Work is grouped by theme. Each item should be small enough to complete and verify on its own;
see `AGENTS.md` for the workflow and `MEMORY.md` for the decisions behind these plans.
## Forward+ desktop preset and a volumetric flashlight (v0.7.1)
The Compatibility renderer has no volumetrics, so the flashlight is a tight pool of light with
no visible shaft. A second desktop build can render it properly without touching the CI/default
path.
- [ ] Write the preset as an `override.cfg` (Godot reads it beside the binary/project) that sets
`rendering/renderer/rendering_method="forward_plus"`, enables `volumetric_fog_*` and SDFGI,
and keeps the Compatibility build as the default (`project.godot` unchanged).
- [ ] Gate the effect in code: only give the flashlight a volumetric component when the renderer
is Forward+ (`RenderingServer.get_rendering_device() != null`), so Web/CI builds are
untouched and the existing headless tests keep passing.
- [ ] Tune `light_volumetric_fog_energy`/`fog_density` for the LATE phase and re-check the
entrance so the shaft does not wash the facade again (the reason the wide cone was cut).
- [ ] A/B the two renderers with `capture_realism.gd` (same stages, two `--out` dirs) and record
the diff + judge scores in `docs/screens/realism/<date>-forward-plus/`.
- [ ] Add the preset to `tools/build_releases.sh` and `docs/RELEASING.md` as an optional
"desktop HQ" artifact, and note the runtime requirement (Vulkan driver) in `README.md`.
- [ ] Test: a headless run must still boot when Forward+ is unavailable (the preset must degrade
to Compatibility instead of failing to start).
## Realism follow-ups (deferred)
- [ ] Re-run the screen-design judge for `12-joke-shop` / `13-studio-arrival` when
`OPENROUTER_API_KEY` is valid; the 2026-11-01 attempt failed with an invalid-key HTTP 400.
- [ ] Re-run the screen-design judge for `12-joke-shop`, `13-studio-arrival` and the
Forward+ A/B when `OPENROUTER_API_KEY` is valid; the 2026-11-01 attempt failed with an
invalid-key HTTP 400.
- [ ] Capture a hooded/winter NPC close-up once a scenario spawns types 5-7 (the explicit
`--portrait-type` override is already in `capture_realism.gd`).
- [ ] Human-authored tree/hedge silhouettes: the whole scene now reads well except the foliage,
+10 -2
View File
@@ -15,7 +15,8 @@ time you export, either:
`Godot_v4.3-stable_export_templates.tpz` contents into
`~/.local/share/godot/export_templates/4.3.stable/`.
Verify with:
The templates must match the editor binary you use locally. CI pins Godot 4.3;
the local development machine uses 4.7.2 with its matching templates. Verify with:
```bash
godot --headless --export-release "Linux" build/gig-line-smoke.x86_64
@@ -86,6 +87,8 @@ Artifacts are written to `build/` (all exports embed the PCK — single files):
- `build/gig-line-macos-<version>.zip`
- `build/gig-line-web-<version>.zip` — browser build (non-threaded Godot web
template; unzip and serve the folder, no COOP/COEP headers required)
- `build/gig-line-desktop-hq-<version>.zip` — optional Forward+ Windows/Linux
build with `override.cfg`; requires a Vulkan-capable GPU/driver
When run in CI (tags/dispatch), the build job also packages:
@@ -94,6 +97,10 @@ When run in CI (tags/dispatch), the build job also packages:
- `build/gig-line-<version>-x86_64.AppImage` — Linux AppImage (`packaging/linux/build_appimage.sh`,
appimagetool via `APPIMAGE_EXTRACT_AND_RUN`)
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.
Locally you can build the AppImage with `bash packaging/linux/build_appimage.sh <version>`;
the NSIS installer requires `makensis` (covered by CI).
@@ -114,7 +121,8 @@ GITEA_TOKEN=your_api_token ./tools/publish_release.sh 0.2.1
The script creates (or reuses) the release from `project.godot`'s
`config/version`, uploads the three portable artifacts **plus** the NSIS
installer and AppImage when present, and prints the release URL. If CI already
installer, AppImage, Web zip and Desktop HQ zip when present, and prints the
release URL. If CI already
published the release, re-running the script only adds missing assets.
> **Host note**: the release is fully built by CI, but anonymous users can only
+4 -4
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.7.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.7.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).
## 3. Context and Scope
@@ -146,8 +146,8 @@ Future options include external analytics, cloud saves, or replay sharing, but t
A continuous-integration pipeline keeps the headless test suite green on every change and publishes tagged releases.
- **Workflow:** `.gitea/workflows/build.yml` runs on pushes, pull requests, tags, and manual dispatches. The `checks` job installs Godot 4.3, imports the project, and runs `res://src/test/test_runner.gd`; it fails on a non-zero exit code or any `SCRIPT ERROR` in the output.
- **Build job:** runs for tags and manual dispatches. It installs export templates, exports Linux/Windows/macOS and the non-threaded Web build, packages the NSIS installer and Linux AppImage, verifies the installer contents, and uploads the artifacts.
- **Publish job:** tag builds call `tools/publish_release.sh` to create the Gitea release and upload the six artifacts. The script uses `GITEA_TOKEN` when configured and otherwise reuses the runner's checkout extraheader credentials.
- **Build job:** runs for tags and manual dispatches. It installs export templates, exports Linux/Windows/macOS and the non-threaded Web build, packages the NSIS installer, Linux AppImage and optional Desktop HQ zip, verifies the installer contents, and uploads the artifacts.
- **Publish job:** tag builds call `tools/publish_release.sh` to create the Gitea release and upload the standard artifacts plus the optional Desktop HQ zip. The script uses `GITEA_TOKEN` when configured and otherwise reuses the runner's checkout extraheader credentials.
- **Hosting:** the repository lives on Gitea (`https://git.allucanget.biz/zwitschi/security-game`). Export templates must match the pinned Godot version; macOS builds are unsigned and need quarantine guidance or notarization for wider distribution.
### Release management
@@ -160,7 +160,7 @@ Releases are produced from a tagged commit and published to the repository host'
- Packaging — NSIS builds the Windows installer; `packaging/linux/build_appimage.sh` builds the AppImage.
- [`RELEASING.md`](RELEASING.md) — step-by-step guide and release checklist.
The release process is: bump the version, run the full test suite and `tools/check_screenshots.sh`, run `tools/build_releases.sh`, tag and push (CI publishes), then verify the README "Downloads" links and the six artifacts.
The release process is: bump the version, run the full test suite and `tools/check_screenshots.sh`, run `tools/build_releases.sh`, tag and push (CI publishes), then verify the README "Downloads" links and the release artifacts.
## 8. Cross-Cutting Concepts
+6 -2
View File
@@ -186,7 +186,8 @@ python3 tools/scene_concepts.py realism --run docs/screens/realism/<run> --judge
```
`capture_realism.gd` runs six stages (day, portrait, evening, late, Joke Shop, studio);
`--stages=day,late` limits it while tuning. The `portrait` shot freezes one NPC on open sidewalk
`--stages=day,late` limits it while tuning; `--flashlight` turns on the guard lamp for
night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one NPC on open sidewalk
and frames the face, which is the only capture that can judge avatar detail
(`--portrait-type=N` picks a role). The texture-adoption run,
[`docs/screens/realism/2026-11-01-adopt/`](../docs/screens/realism/2026-11-01-adopt/report.md),
@@ -194,7 +195,10 @@ compares the pre-adoption captures with the current build: on the shots both set
judge's like-for-like means rose on every sub-score (overall 0.8 → 1.0, backdrop 0.6 → 1.6) and
the Pillow metric gap fell 0.444 → 0.348. The character pass
([`2026-11-01-npc/`](../docs/screens/realism/2026-11-01-npc/report.md)) adds the portrait shot and
records the new faces, hands and sleeve cuffs. The gear-fit run
records the new faces, hands and sleeve cuffs. The
Forward+ A/B ([`2026-11-01-forward-plus/`](../docs/screens/realism/2026-11-01-forward-plus/report.md))
compares the Compatibility entrance with the optional Desktop HQ preset.
The gear-fit run
([`2026-11-01-gear/`](../docs/screens/realism/2026-11-01-gear/report.md)) checks
the sunglasses, bag and helmet against measured head/skirt bounds with
`python3 tools/blender_assets.py fit`; it also fixed the portrait yaw and hid
+1
View File
@@ -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 now releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status.
- Desktop HQ (v0.7.1): optional Forward+ Windows/Linux zip with volumetric fog, SDFGI and a volumetric flashlight; the default Compatibility/Web builds and headless tests gate the effect off. A/B captures live in `docs/screens/realism/2026-11-01-forward-plus/`.
- Venue geometry (v0.7.0): the Joke Shop now builds as a narrow brick alley with a warm wall lantern, shutters, dumpster/AC clutter and no skyline; the TV Studio builds a stone facade, bulb canopy, glossy black car and barrier line. `WorldBuilder.VENUE_SPECS` drives facade/street/nav dimensions, and `GameManager` rebuilds only the world container when the venue changes.
- Gear fit (post-v0.6.0): Blender gear is measured against the avatar head/skirt silhouette and the checks run in both the Python tooling and the headless suite. Sunglasses, the shoulder bag and the firefighter helmet were fitted to the new faces; the Influencer portrait and gallery were recaptured with a before/after report.
+1 -1
View File
@@ -2,7 +2,7 @@
## Setup
1. Download the Godot 4.3 editor (official build) and put `godot` on your PATH.
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.
2. Open or run the project from this directory.
## Run and Test
@@ -0,0 +1,82 @@
{
"run": "docs/screens/realism/2026-11-01-forward-plus-eval",
"judge_model": null,
"sets": {
"baseline": {
"entrance_late": {
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
"metrics": {
"edge_density": 21.3,
"fine_detail": 9.55,
"contrast": 68.03,
"saturation_mean": 127.45,
"colour_spread": 67.44
},
"target_metrics": {
"edge_density": 8.43,
"fine_detail": 3.51,
"contrast": 27.34,
"saturation_mean": 159.44,
"colour_spread": 26.61
},
"metric_gap": 1.568
},
"street_late": {
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
"metrics": {
"edge_density": 10.57,
"fine_detail": 6.21,
"contrast": 26.84,
"saturation_mean": 74.1,
"colour_spread": 26.55
},
"target_metrics": {
"edge_density": 8.83,
"fine_detail": 4.61,
"contrast": 33.26,
"saturation_mean": 188.16,
"colour_spread": 35.38
},
"metric_gap": 0.247
}
},
"candidate": {
"entrance_late": {
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
"metrics": {
"edge_density": 19.31,
"fine_detail": 8.37,
"contrast": 55.27,
"saturation_mean": 104.53,
"colour_spread": 54.55
},
"target_metrics": {
"edge_density": 8.43,
"fine_detail": 3.51,
"contrast": 27.34,
"saturation_mean": 159.44,
"colour_spread": 26.61
},
"metric_gap": 1.187
},
"street_late": {
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
"metrics": {
"edge_density": 11.4,
"fine_detail": 6.21,
"contrast": 27.58,
"saturation_mean": 77.86,
"colour_spread": 27.49
},
"target_metrics": {
"edge_density": 8.83,
"fine_detail": 4.61,
"contrast": 33.26,
"saturation_mean": 188.16,
"colour_spread": 35.38
},
"metric_gap": 0.258
}
}
}
}
@@ -0,0 +1,42 @@
# Realism report: `docs/screens/realism/2026-11-01-forward-plus`
| set | shot | target | gap ↓ | edges | detail | contrast | overall | lighting | materials | texture | backdrop |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| baseline | entrance_late | hollywood_entrance_rain_night | 1.568 | 21.3 | 9.55 | 68.03 | - | - | - | - | - |
| baseline | street_late | boulevard_skyline_dusk | 0.247 | 10.57 | 6.21 | 26.84 | - | - | - | - | - |
| candidate | entrance_late | hollywood_entrance_rain_night | 1.187 | 19.31 | 8.37 | 55.27 | - | - | - | - | - |
| candidate | street_late | boulevard_skyline_dusk | 0.258 | 11.4 | 6.21 | 27.58 | - | - | - | - | - |
Mean scores per set (all captured shots):
| set | shots | gap ↓ | overall | lighting | materials | texture_detail | backdrop | characters |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| baseline | 2 | 0.907 | - | - | - | - | - | - |
| candidate | 2 | 0.723 | - | - | - | - | - | - |
## Desktop HQ A/B
`baseline` is the normal GL Compatibility build; `candidate` is the same
staging with `packaging/desktop_hq/override.cfg` beside the project (Forward+,
SDFGI and volumetric fog). Both use `--stages=late --flashlight`.
The entrance metric gap improved **1.568 → 1.187** and the two-shot mean
**0.907 → 0.723**. The flashlight now lights the door instead of disappearing
into the dark, while the LATE volumetric density (0.020) leaves the brick
facade readable instead of washing it out. `street_late` is nearly unchanged
(0.247 → 0.258), which is expected because the fog is tuned around the
entrance.
Capture command:
```bash
cp packaging/desktop_hq/override.cfg ./override.cfg
xvfb-run -a godot --path . --script res://src/test/capture_realism.gd -- \
--stages=late --flashlight \
--out=res://docs/screens/realism/2026-11-01-forward-plus/candidate
rm override.cfg
```
The vision judge could not run because `OPENROUTER_API_KEY` is invalid; the
retry is recorded in `TODO.md`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

+16
View File
@@ -0,0 +1,16 @@
Gig Line — Desktop HQ build
===========================
This build uses Godot's Forward+ renderer instead of the default
GL Compatibility renderer, enabling volumetric fog and SDFGI.
Requirements:
- A Vulkan-capable GPU/driver (Linux or Windows).
- Keep override.cfg next to the executable. Without it the binary falls back
to the normal Compatibility build.
The game still runs if Forward+ is unavailable, but the desktop-only effects
will be disabled.
Linux: chmod +x gig-line-linux-*.x86_64 && ./gig-line-linux-*.x86_64
Windows: run gig-line-windows-*.exe
+12
View File
@@ -0,0 +1,12 @@
; Gig Line desktop HQ preset. Place this file next to the exported executable.
; It switches only that copy to Forward+; project.godot and the default builds
; stay on GL Compatibility for Web/CI parity.
[rendering]
renderer/rendering_method="forward_plus"
renderer/rendering_method.mobile="forward_plus"
environment/volumetric_fog/volume_size=128
environment/volumetric_fog/volume_depth=128
environment/volumetric_fog/use_filter=1
global_illumination/sdfgi/frames_to_converge=4
global_illumination/sdfgi/frames_to_update_lights=2
+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.7.0"
config/version="0.7.1"
run/main_scene="res://scenes/main.tscn"
config/features=PackedStringArray("4.3", "GL Compatibility")
+9 -6
View File
@@ -54,7 +54,7 @@ const PHASE_LIGHTING := {
"sky_ground": Color(0.45, 0.5, 0.55), "ambient": Color(0.75, 0.78, 0.85), "night": false,
"exposure": 0.6, "glow": 0.45, "fog_color": Color(0.87, 0.83, 0.76),
"fog_density": 0.0034, "fog_energy": 0.5, "contrast": 1.05, "saturation": 1.04,
"ambient_energy": 0.1,
"ambient_energy": 0.1, "volumetric_density": 0.004, "volumetric_energy": 0.0,
},
Rules.Phase.MIDDAY: {
"sun_energy": 1.0, "sun_angle": -62.0, "sun_yaw": -30.0, "sun_color": Color(1.0, 0.99, 0.96),
@@ -62,7 +62,7 @@ const PHASE_LIGHTING := {
"sky_ground": Color(0.5, 0.54, 0.58), "ambient": Color(0.85, 0.88, 0.95), "night": false,
"exposure": 0.6, "glow": 0.4, "fog_color": Color(0.88, 0.87, 0.83),
"fog_density": 0.0028, "fog_energy": 0.5, "contrast": 1.06, "saturation": 1.04,
"ambient_energy": 0.28,
"ambient_energy": 0.28, "volumetric_density": 0.003, "volumetric_energy": 0.0,
},
# Evening is dusk, not a bright sunset: the sun is almost down and the street
# is carried by the marquee, the lamps and the lit windows (realism pass).
@@ -72,7 +72,7 @@ const PHASE_LIGHTING := {
"sky_ground": Color(0.1, 0.09, 0.12), "ambient": Color(0.2, 0.19, 0.26), "night": true,
"exposure": 0.72, "glow": 0.8, "fog_color": Color(0.34, 0.27, 0.3),
"fog_density": 0.0026, "fog_energy": 0.45, "contrast": 1.1, "saturation": 1.05,
"ambient_energy": 0.12,
"ambient_energy": 0.12, "volumetric_density": 0.014, "volumetric_energy": 0.10,
},
Rules.Phase.LATE: {
"sun_energy": 0.02, "sun_angle": -8.0, "sun_color": Color(0.3, 0.35, 0.5),
@@ -80,7 +80,7 @@ const PHASE_LIGHTING := {
"sky_ground": Color(0.03, 0.03, 0.05), "ambient": Color(0.06, 0.07, 0.12), "night": true,
"exposure": 0.95, "glow": 1.15, "fog_color": Color(0.08, 0.1, 0.2),
"fog_density": 0.0028, "fog_energy": 0.45, "contrast": 1.12, "saturation": 1.0,
"ambient_energy": 0.22,
"ambient_energy": 0.22, "volumetric_density": 0.020, "volumetric_energy": 0.14,
},
}
var hud: Hud = null
@@ -196,11 +196,14 @@ func _build_guard() -> GuardController:
# Aim straight ahead: tilting it down made the cone hit the ground right in
# front of the guard instead of the person or door being checked.
flashlight.rotation_degrees = Vector3.ZERO
flashlight.spot_range = 20.0
flashlight.spot_range = 30.0 if Materials.modern_light_falloff() else 20.0
flashlight.spot_angle = 14.0
flashlight.light_energy = 4.2
flashlight.spot_attenuation = 0.9
flashlight.spot_attenuation = 0.0 if Materials.modern_light_falloff() else 0.9
flashlight.spot_angle_attenuation = 0.9
# The volumetric shaft is a Forward+ (desktop HQ) effect. Compatibility and
# headless builds leave the light non-volumetric, so their look is unchanged.
flashlight.light_volumetric_fog_energy = 3.0 if RenderingServer.get_rendering_device() != null else 0.0
head.add_child(flashlight)
var collider := CollisionShape3D.new()
var shape := CapsuleShape3D.new()
+34 -1
View File
@@ -152,9 +152,24 @@ static func window_pane() -> StandardMaterial3D:
mat.emission_energy_multiplier = 0.0
return mat
## Godot 4.7 changed the Compatibility light falloff in practice: the same
## attenuation/range that read well on 4.3 leaves the night scene nearly black.
## Use this switch for local lights until the project moves its baseline to 4.7.
static func modern_light_falloff() -> bool:
var info := Engine.get_version_info()
return int(info.get("major", 4)) > 4 \
or (int(info.get("major", 4)) == 4 and int(info.get("minor", 0)) >= 7)
## True only when a real RenderingDevice exists. The Compatibility renderer and
## the headless dummy renderer return null, so the desktop-only effects can be
## gated without Web/CI builds ever enabling them.
static func forward_plus_active() -> bool:
return RenderingServer.get_rendering_device() != null
## Environment look shared by every phase: ACES tonemapping, soft glow, aerial
## fog for depth, and a mild contrast/saturation lift. Phase values come from
## `GameManager.PHASE_LIGHTING`.
## `GameManager.PHASE_LIGHTING`. Volumetric fog and SDFGI are only enabled when
## Forward+ is actually available (the optional desktop preset).
static func configure_environment(env: Environment, cfg: Dictionary) -> void:
if env == null:
return
@@ -180,6 +195,24 @@ static func configure_environment(env: Environment, cfg: Dictionary) -> void:
env.fog_density = float(cfg.get("fog_density", 0.0035))
env.fog_aerial_perspective = 0.25
env.fog_sky_affect = 0.4
var advanced := forward_plus_active()
env.volumetric_fog_enabled = advanced
env.sdfgi_enabled = advanced
if advanced:
# Desktop HQ: a shallow, cool fog gives the torch a visible shaft while
# keeping the entrance facade readable. Phase values are tuned per phase.
env.volumetric_fog_density = float(cfg.get("volumetric_density", 0.012))
env.volumetric_fog_albedo = cfg.get("volumetric_albedo", Color(0.55, 0.62, 0.72))
env.volumetric_fog_emission = cfg.get("volumetric_emission", Color.BLACK)
env.volumetric_fog_emission_energy = float(cfg.get("volumetric_energy", 0.0))
env.volumetric_fog_anisotropy = 0.25
env.volumetric_fog_length = 48.0
env.volumetric_fog_ambient_inject = 0.15
env.volumetric_fog_sky_affect = 0.2
env.sdfgi_use_occlusion = true
env.sdfgi_cascades = 4
env.sdfgi_min_cell_size = 0.2
env.sdfgi_energy = 0.7
env.adjustment_enabled = true
env.adjustment_brightness = 1.0
env.adjustment_contrast = float(cfg.get("contrast", 1.06))
+6 -3
View File
@@ -548,7 +548,8 @@ func _build_bulb_canopy() -> void:
for x in [-2.2, 2.2]:
var omni := OmniLight3D.new()
omni.position = Vector3(x, 5.15, front + 1.4)
omni.omni_range = 8.0
omni.omni_range = 12.0 if Materials.modern_light_falloff() else 8.0
omni.omni_attenuation = 0.0 if Materials.modern_light_falloff() else 1.0
omni.light_energy = 0.0
omni.light_color = Color(1.0, 0.72, 0.36)
root.add_child(omni)
@@ -586,7 +587,8 @@ func _add_wall_lantern(pos: Vector3, with_light: bool) -> void:
var omni := OmniLight3D.new()
omni.name = "LanternGlow"
omni.position = pos + Vector3(0.0, -0.1, 0.45)
omni.omni_range = 9.0
omni.omni_range = 14.0 if Materials.modern_light_falloff() else 9.0
omni.omni_attenuation = 0.0 if Materials.modern_light_falloff() else 1.0
omni.light_energy = 0.0
omni.light_color = Color(1.0, 0.72, 0.38)
root.add_child(omni)
@@ -666,7 +668,8 @@ func _build_street_furniture() -> void:
holder.name = "Lamp_%d" % int(side)
var omni := OmniLight3D.new()
omni.position = Vector3(-0.85, 4.65, 0)
omni.omni_range = 12.0
omni.omni_range = 16.0 if Materials.modern_light_falloff() else 12.0
omni.omni_attenuation = 0.0 if Materials.modern_light_falloff() else 1.0
omni.light_energy = 0.0
omni.light_color = Color(1, 0.93, 0.75)
holder.add_child(omni)
+9
View File
@@ -30,6 +30,9 @@ const HOODED_TYPES := [5, 6, 7] # TRESPASSER, VIOLENT_THREAT, MENTAL_CRISIS
## Optional `--portrait-type=N` picks the NpcType a portrait should feature.
var _portrait_type := -1
## Optional `--flashlight` turns the guard lamp on for night stages; the desktop
## HQ A/B uses it to show the volumetric shaft.
var _flashlight := false
## Where the portrait subject is placed, relative to the guard's spawn.
var _portrait_spot := Vector3(0.0, 0.5, 9.0)
var _portrait_subject: Node3D = null
@@ -114,6 +117,8 @@ func _parse_args() -> void:
_only_stages[name.strip_edges()] = true
elif text.begins_with("--portrait-type="):
_portrait_type = int(text.substr(16))
elif text == "--flashlight":
_flashlight = true
func _pair(text: String, into: Dictionary) -> void:
var cut := text.find("=", 6) # skip the "res://" scheme
@@ -168,6 +173,10 @@ func _begin_stage() -> void:
if venue != "" and venue != str(gm.get("active_scenario_id")):
gm.call("switch_scenario", venue)
gm.coordinator.elapsed_s = float(stage["elapsed"])
if _flashlight:
var want := bool(stage["night"])
if want != bool(gm.flashlight_on):
gm.call("_toggle_flashlight")
_apply(swaps)
if bool(stage["night"]):
_apply(night_swaps)
+4 -1
View File
@@ -43,8 +43,11 @@ func test_flashlight_aims_a_tight_forward_spot() -> bool:
var c := TestUtils.check(lamp != null and lamp.spot_angle <= 20.0,
"flashlight cone is narrow enough to read as a torch")
var d := TestUtils.check(lamp != null and not lamp.visible, "flashlight starts off")
var e := TestUtils.check(lamp != null
and is_zero_approx(lamp.light_volumetric_fog_energy),
"headless/compatibility builds leave the flashlight non-volumetric")
root.free()
return a and b and c and d
return a and b and c and d and e
func test_flashlight_widens_perception() -> bool:
## Without the flashlight, an NPC at AWARE_RADIUS + 3 m is unaware.
+7 -2
View File
@@ -77,14 +77,19 @@ func test_environment_tuning_applies_phase_values() -> bool:
var f := TestUtils.check(
env.fog_light_color.is_equal_approx(late["fog_color"]),
"phase fog colour is used")
return a and b and c and d and e and f
var advanced := Materials.forward_plus_active()
var g := TestUtils.check(env.volumetric_fog_enabled == advanced,
"volumetric fog is gated to Forward+ (advanced=%s)" % advanced)
var h := TestUtils.check(env.sdfgi_enabled == advanced,
"SDFGI is gated to Forward+ (advanced=%s)" % advanced)
return a and b and c and d and e and f and g and h
func test_every_phase_has_environment_keys() -> bool:
var ok := true
for phase in [Rules.Phase.EARLY, Rules.Phase.MIDDAY, Rules.Phase.EVENING, Rules.Phase.LATE]:
var cfg: Dictionary = GameManager.PHASE_LIGHTING.get(phase, {})
for key in ["exposure", "glow", "fog_color", "fog_density", "fog_energy",
"contrast", "saturation"]:
"contrast", "saturation", "volumetric_density", "volumetric_energy"]:
if not cfg.has(key):
ok = false
return TestUtils.check(ok, "all phases set exposure/glow/fog/grading")
+25
View File
@@ -11,6 +11,7 @@
# build/gig-line-windows-<version>.exe
# build/gig-line-macos-<version>.zip
# build/gig-line-web-<version>.zip (Godot web build: static hosting, no threads)
# build/gig-line-desktop-hq-<version>.zip (optional Forward+ Linux/Windows)
#
set -euo pipefail
@@ -49,6 +50,30 @@ with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as archive:
PYZIP
fi
echo "==> Packaging optional Desktop HQ build (Forward+)"
HQ_DIR="build/desktop-hq"
rm -rf "$HQ_DIR"
mkdir -p "$HQ_DIR"
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/"
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" .)
else
python3 - "$PWD/$HQ_DIR" "$PWD/build/gig-line-desktop-hq-${VERSION}.zip" <<'PYZIP'
import os, sys, zipfile
src, out = sys.argv[1], sys.argv[2]
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as archive:
for root, _dirs, files in os.walk(src):
for name in files:
path = os.path.join(root, name)
archive.write(path, os.path.relpath(path, src))
PYZIP
fi
rm -rf "$HQ_DIR"
echo ""
echo "Artifacts:"
ls -la build/*"${VERSION}"*
+1
View File
@@ -67,6 +67,7 @@ extras=(
"build/gig-line-setup-$VERSION.exe"
"build/gig-line-$VERSION-x86_64.AppImage"
"build/gig-line-web-$VERSION.zip"
"build/gig-line-desktop-hq-$VERSION.zip"
)
all_artifacts=("${portables[@]}")
for f in "${extras[@]}"; do