diff --git a/MEMORY.md b/MEMORY.md index d87e5db..dcece53 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -49,13 +49,26 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o ## Handoff (read this first) -- **Visual-alignment pass planned (2026-11-01):** `TODO.md` gains a "Visual - alignment with rendered mockups" section. Goal: restructure generated-image - storage, move the reference mockups out of `docs/screens/designs/` into a - runtime-accessible root (e.g. `assets/images/`), load the proposed UI/scene - art at runtime (start menu, selection screens, HUD, panels), and make the game - match the mockups — verified by `screen_designs.py compare` + `judge`. Not - started; no code moved yet. +- **Status + active passes (2026-11-01, moved from `TODO.md`):** v0.6.0 shipped — the Blender + character/gear/prop library is integrated, AO maps are baked and wired, and the environment + textures are lifted to 1024² with normal maps (see `DONE.md`). Headless **222/222**, Python + **81**; the scheduled presentation pass is complete. Two passes remain in `TODO.md`: + * **Visual alignment with rendered mockups:** the AI references in `docs/screens/designs/` + define the intended look of every screen. The pass reorganizes generated-image storage, + loads the proposed UI/scene art at runtime (`src/game/ui/art.gd`, `assets/images/`), and + aligns the selection screens, HUD, and scene visuals with those mockups — verified with + `screen_designs.py compare` + `judge`. + * **Realism pass** (plan: `PLAN.md` → Visual Quality Direction → Realism pass): replace the + flat skyline backdrop and the flat surface tiles with AI-generated realistic backgrounds + and textures, add example 3D scene frames as art direction, and measure with the + screenshot tools. Rule: never overwrite an existing image — new outputs get new paths + (`next_free_path` -vN suffix). Raw → `assets/textures/ai/v2/raw/` (gitignored); candidates + → `assets/textures/ai/game_ready/v2/` (gitignored pool, only validated picks tracked, + ADR-0014); scene refs → `docs/screens/concepts/`; trials → `docs/screens/realism//`. + Realism report 2026-11-01-v2 mean judge scores: backdrop 2.6 → 3.6 (v2 day + night + skyline) → **4.0** (v2 day + dusk skyline for the Evening phase); texture detail 1.2 → + 1.6–1.75; overall 1.6 → 2.0. Scores swing ±1–2 between runs (trend only); the + `characters` score moved 3.0 → 1.0 with identical NPCs, showing the noise. - **Phase 2 — AI texturing (this agent, DONE 2026-11-01).** Phase 1 assets were already in the tree (untracked) and are preserved. Generated six seamless AI @@ -138,22 +151,36 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o flat plane, so avoidance (not a baked mesh) routes guests around props. Placements live in `PROP_PLACEMENTS` with `placement_problems()` validation; variants come from `variant_for(index)`. `smoke_props.gd` must be windowed because the guard reads captured-mouse input. -- **Realism pass tooling (2026-11-01):** `tools/scene_concepts.py` (`list`/`generate`/`process`/ - `audit`/`realism`) never overwrites an image (`next_free_path` adds `-vN`; `process` skips - existing targets). Raw AI output: `assets/textures/ai/v2/raw/` (gitignored). Candidate pool: - `assets/textures/ai/game_ready/v2/` (gitignored except the validated picks un-ignored in - `.gitignore`: skyline day-v4/dusk-v3/night-v4 plus brick/pavers/asphalt/asphalt_wet + normals). - Concepts: `docs/screens/concepts/` (committed, `.gdignore`, targets in its README). Runs: - `docs/screens/realism//` (PNG captures gitignored; `report*.{json,md}` + `sheet.jpg` - committed). `src/test/capture_realism.gd` swaps textures at runtime by the material's - *original* albedo path (`--swap/--night-swap/--tint/--normal=OLD=NEW`) and writes to - `--out=res://...`. Changing game code is not needed for an A/B test. Gotchas: - gpt-5-image-mini honours `background=transparent` for the 3:2 skyline requests, but it writes - garbled "HOLLYWOOD" lettering unless the prompt forbids signs. Skyline strips are 2:1 to match - one UV repeat of the ring. `elapsed_s = 620` gives the *Evening* phase (pink sky), not night. - The gemini-flash-lite realism scores are noisy (±1–2), and the judge sometimes returns a 400 - "API key not valid" from the provider, which the one retry covers. None of the v2 assets are - wired into the game yet. +- **Realism pass (2026-11-01, completed + adopted):** `tools/scene_concepts.py` + (`list`/`generate`/`process`/`promote`/`audit`/`realism`) never overwrites an image + (`next_free_path` adds `-vN`; `process` skips existing unless `--new`; `promote` keeps an + existing pick). Raw AI output: `assets/textures/ai/v2/raw/` (gitignored). Candidate pool: + `assets/textures/ai/game_ready/v2/` (gitignored) and the **tracked runtime assets** are + `.../v2/picks/{skyline_day,skyline_dusk,skyline_night,brick_facade,sidewalk_pavers,asphalt, + asphalt_wet,awning_canvas}.png` (+ `_normal`). Concepts `docs/screens/concepts/` (committed, + `.gdignore`, targets in its README). Runs `docs/screens/realism//` (PNG captures + gitignored; `report.{json,md}` + `sheet.jpg` committed). + `src/test/capture_realism.gd` runs five stages (day 0 s / evening 620 s / late 760 s / + joke_shop 700 s / studio 700 s; `--stages=` subset) and swaps textures by the material's + *original* albedo path. **Gotcha:** in the SceneTree script, do not overwrite `_phase` after + `_begin_stage()` sets `done`, or the run loops forever between stage/shoot (`_queue` empty). + **Gotcha:** `elapsed_s = 620` is *Evening* (dusk), 700+ is Late. Godot 4.3 + `StandardMaterial3D` has no `fog_enabled`; the skyline is a `ShaderMaterial` with + `render_mode unshaded, cull_disabled, fog_disabled` + `ALPHA_SCISSOR_THRESHOLD` so the + panorama's own haze shows. Backdrop plane is 90 m tall with 2 UV repeats (~200×90 m per + repeat, near the 2:1 panorama aspect). Legacy `assets/textures/skyline.png` is no longer + loaded but stays on disk for the `test_world` asset check. + **Phase look:** day sun 0.62 / exposure 0.6 / ambient 0.22; evening sun 0.12 / exposure + 0.72 / dark violet sky; late exposure 0.95 / ambient 0.10 with the wet asphalt street and a + bright skyline tint (1.5) so the night windows read. Stripes: `stripe_period()` (detrended + autocorrelation) + `align_stripes()` (resample to whole periods) replace the edge crossfade + for `awning_canvas`; `audit_striped_tile()` compares the wrap step with the harshest interior + step. + **Judge:** `google/gemini-2.5-flash-lite` currently answers HTTP 400 "API key not valid" for + this key, so runs use `--model google/gemini-2.5-flash`; scores are noisy (±1–2), so + `realism --repeats 3` averages and `summarize()` reports a like-for-like mean over the shots + every set captured. Final: overall 0.8→1.0, backdrop 0.6→1.6, metric gap 0.444→0.348. + `screen_designs.py judge` gained one retry for the same transient 400. - **Blender asset library (v0.6.0, integrated 2026-11-01):** `tools/blender_assets.py` (`build`/`render` in Blender; `audit`/`sheet`/`list` stdlib) writes `assets/models/blender/{npc,npc_gear,props}/*.glb`, `manifest.json` (integration source of @@ -212,6 +239,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o - Radio, wand, and body-camera timeline panels use their reference art as backdrops; forms sit on transparent, padded panels over the covered mockup controls. - HUD target names use a boxed chip under the crosshair; escalation has a five-segment bar below the text label. - Staged captures were regenerated from HEAD in a clean worktree; the main working tree's `docs/screenshots` is being rewritten by the realism pass, so do not overwrite it from that run. +- `tools/check_screenshots.sh` is the repeatable capture+compare smoke; both capture scripts accept `--out=res://docs/screenshots`. Judge verdicts on the staged set were 7 match / 4 review / 0 diff. ## Current State diff --git a/README.md b/README.md index a4a0e1a..2d92529 100644 --- a/README.md +++ b/README.md @@ -287,11 +287,11 @@ Everything below is audited and shipped in the game; development-only outputs | | | | --- | --- | -| ![brick facade](assets/textures/ai/game_ready/brick_facade.png) | ![concrete](assets/textures/ai/game_ready/concrete.png) | -| ![asphalt](assets/textures/ai/game_ready/asphalt.png) | ![awning](assets/textures/ai/game_ready/awning.png) | -| ![skyline](assets/textures/ai/game_ready/skyline.png) | ![vignette](assets/textures/ai/game_ready/vignette.png) | +| ![brick facade](assets/textures/ai/game_ready/v2/picks/brick_facade.png) | ![sidewalk pavers](assets/textures/ai/game_ready/v2/picks/sidewalk_pavers.png) | +| ![asphalt](assets/textures/ai/game_ready/v2/picks/asphalt.png) | ![awning](assets/textures/ai/game_ready/v2/picks/awning_canvas.png) | +| ![skyline day](assets/textures/ai/game_ready/v2/picks/skyline_day.png) | ![skyline night](assets/textures/ai/game_ready/v2/picks/skyline_night.png) | -- **Surface textures:** the six tiles above, loaded by `src/game/materials.gd` and `WorldBuilder`. +- **Surface textures:** the v2 picks above (brick, sidewalk pavers, dry and wet asphalt, awning canvas) plus their normal maps, loaded by `src/game/materials.gd` and `WorldBuilder`; the three skyline panoramas are switched per shift phase. The first-generation tiles and `assets/textures/skyline.png` are no longer loaded. - **UI art:** `assets/textures/ai/game_ready/icons/` (flashlight, door lock, wand, REC) and `assets/textures/ai/game_ready/cards/` (ID, wristband, ticket, credential, guest list), loaded by `src/game/ui/hud.gd`. - **Generated models:** `assets/models/generated/*.glb` — bollard, bench, trash bin, planter, newspaper box, hydrant, and the NPC base — plus their colour atlases. - **NPC material maps:** `assets/textures/npc/*_{albedo,normal,orm}.png` and `palette.json`. @@ -304,12 +304,50 @@ python3 tools/model_pipeline.py audit python3 tools/npc_pipeline.py audit ``` +### Realism pass: scene concepts, skylines and textures + +The skyline, surface tiles and phase lighting were rebuilt against AI concept frames. The +concepts live in [`docs/screens/concepts/`](docs/screens/concepts/README.md); the validated +picks the game loads are in `assets/textures/ai/game_ready/v2/picks/` (day/dusk/night +panoramas, brick, sidewalk pavers, dry and wet asphalt, awning canvas, each with a normal +map). + +`tools/scene_concepts.py` generates and processes new candidates, promotes the validated ones +to stable names, audits them, and scores captures against the concepts. It never overwrites an +image: new files get a `-vN` suffix, and `process` skips existing candidates unless `--new`. + +```bash +python3 tools/scene_concepts.py generate --group scene # or backdrop / texture, --variants N +python3 tools/scene_concepts.py process && python3 tools/scene_concepts.py audit +python3 tools/scene_concepts.py promote # validated picks -> v2/picks/ +godot --headless --path . --import + +# A/B a candidate without touching game code: the same cameras, textures swapped at runtime. +godot --path . --script res://src/test/capture_realism.gd -- --out=res://docs/screens/realism//after +godot --path . --script res://src/test/capture_realism.gd -- --out=res://docs/screens/realism//try \ + --swap=res://assets/textures/ai/game_ready/v2/picks/brick_facade.png=res://assets/textures/ai/game_ready/v2/brick_facade-v2.png + +# Score before/after against the concepts (Pillow detail metrics + vision judge). +python3 tools/scene_concepts.py realism --run docs/screens/realism/ --judge \ + --model google/gemini-2.5-flash --repeats 3 +``` + +`capture_realism.gd` runs five stages (day, evening, late, Joke Shop, studio); `--stages=day,late` +limits it while tuning. The latest run, +[`docs/screens/realism/2026-11-01-adopt/`](docs/screens/realism/2026-11-01-adopt/report.md), +compares the pre-adoption captures with the current build: on the shots both sets captured the +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. + +![realism run](docs/screens/realism/2026-11-01-adopt/sheet.jpg) + ### Screen design references Screen mockups and screenshot comparison use `tools/screen_designs.py`. It turns the screen descriptions in `docs/` (README captions, ADRs 0007–0010, `PLAN.md`, and -`architecture.md`) into reference images under `docs/screens/designs/`, then uses -those images as the expected state to check the game's captures. +`architecture.md`) into runtime reference art under `assets/images/ui/` (with a +reference-only snapshot in `docs/screens/designs/`), then uses those images as +the expected state to check the game's captures. Each screen is tagged `scene` (a realistic first-person 3D world view) or `ui` (an in-game panel overlaid on a softened scene), and the generator appends a @@ -368,6 +406,10 @@ default 0.6 — expected when a concept mockup is more stylised than the game), `compare` after regenerating the game screenshots with `src/test/capture_screenshots.gd`. +For a one-command drift check, run `tools/check_screenshots.sh`. It renders the +staged captures and prop gallery into `docs/screenshots/` (using `xvfb-run` when +no display is available) and then runs `screen_designs.py compare`. + ### Setup 1. Download the Godot 4.3 editor (official build) and put `godot` on your PATH. @@ -484,7 +526,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: **208 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: **227 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`) diff --git a/TODO.md b/TODO.md index a87995a..646607e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,10 @@ # TODO Godot 4.3 prototype. Latest release **v0.6.0** (Blender character/gear/prop library, baked AO, environment lift). -Headless suite **222/222** plus **81** Python tooling tests; the session, PLAN-verb, crowd, emergency, +Headless suite **227/227** plus **86** Python tooling tests; the session, PLAN-verb, crowd, emergency, timeline, audio, scenario-content, props, NPC-budget, movement, and windowed focus/radial smokes -pass. Anonymous downloads are enabled on the host. +pass. Anonymous downloads are enabled on the host. The realism pass is adopted (per-phase skyline +panoramas, v2 surface tiles, retuned lighting); see `DONE.md` and `TODO.md` → Realism follow-ups. The scheduled presentation pass is complete (see `DONE.md`). The next scheduled pass is the graphics-quality work in [`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md). @@ -14,13 +15,6 @@ graphics-quality work in [`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6. ## Visual alignment with rendered mockups -The AI-rendered references in `docs/screens/designs/` (start menu, pause, settings, -shift review, radial menu, HUD, scene views) describe the intended look of every -screen. This pass reorganizes how generated images are stored, makes the game load -the proposed interface/scene art from disk, and aligns the selection screens, HUD, -and scene visuals with those mockups — verified with -`tools/screen_designs.py compare` + `judge`. - ### Storage restructure - [x] Decide the canonical runtime image root (`assets/images/` for UI/scene art): recorded in @@ -64,8 +58,8 @@ and scene visuals with those mockups — verified with ### Verify against the mockups - [x] Regenerate the staged captures and run `screen_designs.py compare` (11 good, 0 bad; verified in a clean HEAD worktree). -- [ ] Run `screen_designs.py judge` and clear every `diff` verdict (target `match`/`review`). -- [ ] Add the capture + compare step to CI or a repeatable smoke so drift is caught early. +- [x] Run `screen_designs.py judge` and clear every `diff` verdict (7 match, 4 review, 0 diff). +- [x] Add the capture + compare step as a repeatable smoke so drift is caught early (`tools/check_screenshots.sh`). ## Fix mouse movement bug @@ -75,128 +69,22 @@ and scene visuals with those mockups — verified with - [ ] Improve the visual appearance of in-game pop-ups (layout, button style, palette, icons, and text styling). -## Realistic backgrounds, textures and scene references +## Realism follow-ups (v0.6.1 candidates) -Goal: replace the outdated flat skyline backdrop (`assets/textures/skyline.png`, black boxes -with dot windows) and the flat-looking surface tiles with AI-generated, more realistic -backgrounds and textures. Add a larger set of example 3D scene images as art direction, and -measure the in-game result against that target with the screenshot tools. Plan: -[`PLAN.md` → Visual Quality Direction → Realism pass](PLAN.md#realism-pass). - -**Rule for every step: never overwrite an existing image.** New outputs get new paths. When a -path is taken, the tools pick the next free `-vN` suffix. Raw AI output stays on disk under -`assets/textures/ai/v2/raw/` (gitignored like the other raw AI folders). Processed game-ready -candidates go to `assets/textures/ai/game_ready/v2/`, a local pool where only the validated picks -are tracked (ADR-0014). Scene references go to `docs/screens/concepts/`, and trial captures and -reports to `docs/screens/realism//`. - -### Step 1 — Tooling (`tools/scene_concepts.py`) - -- [x] Add a catalogue of prompts in three groups: `scene` (16:9 example 3D game frames), - `backdrop` (4:1 skyline panoramas with transparent sky), and `texture` (seamless PBR - surface tiles). Each entry has a name, prompt, role and target path. -- [x] `generate [--group] [--only] [--variants N]`: call the OpenRouter image API through - `generate_textures.generate_image` (key from the repo `.env`), save raw output, and never - overwrite (`next_free_path`). Print every file written. -- [x] `process`: turn raw backdrops into 2048×512 RGBA strips that tile horizontally (edge - crossfade, sky made fully transparent, hard alpha for the alpha-scissor material). Turn raw - textures into seamless 1024² tiles (`asset_pipeline.make_seamless`) plus a normal map - (`make_normal_maps.normal_map`). Skip outputs that already exist. -- [x] `audit`: run `asset_pipeline.audit_image` on the processed candidates (`backdrop` / - `tile` roles) and fail on any bad result. -- [x] `realism`: for each trial capture, report the Pillow detail metrics (edge density, - contrast, colour spread, saturation) next to the matching concept image. Optionally ask a - vision model (`google/gemini-2.5-flash-lite`) for 0–10 realism scores (lighting, - materials, texture detail, backdrop, overall) with concrete fixes. Write a new JSON + - Markdown report per run. -- [x] Unit tests (`tools/test_scene_concepts.py`): catalogue shape, the no-overwrite path - picker, backdrop/tile processing on synthetic images, metrics, and the judge payload/parse - (offline, no network). - -### Step 2 — Example 3D scene images - -- [x] Generate 3D-game-style example frames for every venue and time of day: Hollywood entrance - (morning, golden hour, rainy night), boulevard skyline view (day, dusk), Joke Shop alley - (evening, close-up of the door), studio stage door (night arrival), sidewalk material - close-up, entrance steps and podium, crowd-control barrier line, and a responder arrival - wide shot. -- [x] Review the set and record the visual targets each frame sets (sky, haze, material detail, - backdrop depth) in the report. - -### Step 3 — Backgrounds (replace the outdated skyline) - -- [x] Generate skyline panoramas for day, dusk and night (lit windows): Hollywood/Los Angeles - mid-rise blocks, palm silhouettes, the hills on the horizon, a transparent sky, and a - straight-on view, in several variants each. -- [x] Process them into `assets/textures/ai/game_ready/v2/skyline_{day,dusk,night}*.png`, - seamless horizontally for the 3× UV repeat around the backdrop ring. Strips are 2048×1024 - (2:1, one UV repeat of the 400×60 m ring is about 133×60 m). 12 candidates, 11 audit - clean; `skyline_day-v3` misses the seam limit by 0.6. Variants 1–2 contain garbled - "Hollywood" lettering, so the prompt now forbids signs. Picks: `skyline_day-v4`, - `skyline_dusk-v3`, `skyline_night-v4`. - -### Step 4 — Textures - -- [x] Generate higher-detail seamless tiles: red brick facade, granite/limestone facade - (studio), sidewalk concrete pavers with joints, asphalt with aggregate, wet night asphalt, - stone steps, striped awning canvas, and stucco wall. -- [x] Process them into 1024² game-ready tiles plus normal maps in - `assets/textures/ai/game_ready/v2/` and audit them (16 tiles + 16 normals, all good). The - `awning_canvas` seam crossfade smears the stripes at the tile edge; it needs a - stripe-aligned crop. - -### Step 5 — In-game trial captures (no game-code change) - -- [x] Add `src/test/capture_realism.gd`. It boots the Hollywood venue, captures a fixed shot - list (entrance day, boulevard/skyline day, boulevard night), and can swap textures at - runtime from `--skyline=` / `--brick=` / `--concrete=` / `--asphalt=` user arguments. The - swap finds materials by their current texture, so baseline and candidate frames use the - same camera. Writes to `user://screenshots/realism//`. -- [x] Capture the baseline (current assets) and the candidate set (v2 skyline + textures), and - copy them into `docs/screens/realism//{baseline,candidate}/`. - -### Step 6 — Compare against the realistic target - -- [x] Run `scene_concepts.py realism` on baseline and candidate. Record the metric deltas and the - vision scores, and note which candidate textures/backdrops win. Run - `docs/screens/realism/2026-11-01-v2/` (`report-v2.md`, `sheet.jpg`) gives these mean - judge scores: backdrop 2.6 → 3.6 (v2 day + night skyline) → **4.0** (v2 day + dusk - skyline for the Evening phase), texture detail 1.2 → 1.6–1.75, overall 1.6 → 2.0. Single - scores swing by ±1–2 between runs, so read them as trends. The `characters` score - moved from 3.0 to 1.0 with identical NPCs, which shows the noise. -- [x] Note the remaining realism gaps (sky/fog, lighting, building geometry, NPC fidelity) as - follow-up tasks below. - -### Step 6b — Follow-ups from the 2026-11-01 realism report - -- [ ] Fix sidewalk overexposure: the concrete reads near-white in every day shot, while the - concepts keep the ground mid-grey. Lower the albedo tint or the sun energy, or add tonemap - exposure in `Materials.configure_environment`. -- [ ] Make the Evening/Late phases darker and lit by practical lights (marquee, lamps, - windows), and add wet-asphalt roughness at night, as in the rain-night concept. -- [ ] Choose the skyline per phase (day → `skyline_day`, Evening → `skyline_dusk`, Late → - `skyline_night`) and lower the fog density on the backdrop so the night windows show. -- [ ] Raise the backdrop plane (60 m → ~90 m) or use 2 UV repeats so the skyline is not a - thin strip at the horizon. -- [ ] Add facade depth (plinth, cornice, window reveals) and replace the blob trees and shrubs - with denser foliage meshes (Blender library follow-up). -- [ ] Re-cut `awning_canvas` so its stripes align at the tile edge. -- [ ] Capture shots from the Joke Shop and the studio too (add them to `capture_realism.gd` / - `SHOTS`). - -### Step 7 — Adopt the winners (after the in-flight normal-map/AO work is committed) - -- [x] Track the validated picks in place (ADR-0014). `game_ready/v2/*` is a gitignored local - pool; `.gitignore` un-ignores the skyline picks and the brick/pavers/asphalt (+ wet) tiles - and their normals. Never replace the existing `game_ready/` files. -- [ ] Point `WorldBuilder.T_SKYLINE` at the chosen v2 skyline (separate day/night textures through - `set_night_mode`), and retune `albedo_color` for the realistic strip. -- [ ] Switch `materials.gd` / `world_builder.gd` surface preloads to the chosen v2 tiles + normal - maps, keeping the current UV scales. Update `test_materials.gd` / `test_world.gd`. -- [ ] Regenerate `docs/screenshots/` with `capture_screenshots.gd`, then run - `screen_designs.py compare` + `judge` and `scene_concepts.py realism`. There must be no - `diff` verdict regressions, and realism scores must not drop. -- [ ] Update `README.md` (tool usage + gallery), `MEMORY.md` and `docs/architecture.md`. +- [ ] Fixed ground exposure for the entrance view: it is still ~20 % brighter than the concept. + Either put the entrance in the building's shadow (sun yaw/elevation) or add a slight + per-view exposure so the shaded sidewalk reads mid-grey. +- [ ] Make the flashlight read in the dark: the `06-night-flashlight` concept shows a visible + beam, but a Compatibility spot light only brightens the near wall. A cheap fake cone (an + additive mesh or a light-shaft quad) would sell it, and the night mockup expects it. +- [ ] NPC skin/clothing still reads as low-poly next to the concepts (judge `characters` ≈ 0–1). + The Blender library has the budgets for one more detail pass (fingers, sleeves, faces). +- [ ] The `screen_designs.py judge` default model (`google/gemini-2.5-flash-lite`) currently + fails with a provider "API key not valid" 400; the tool retries once. Re-check before the + next release and either pin a working model or document `--model`. +- [ ] Add the two venue stages to the committed gallery (they are capture-only today) and give + the Joke Shop and studio their own geometry so their concept comparisons are geometric, + not just lighting. ## Future ideas (optional, not scheduled) diff --git a/src/test/capture_prop_gallery.gd b/src/test/capture_prop_gallery.gd index 73a8eba..351ef6a 100644 --- a/src/test/capture_prop_gallery.gd +++ b/src/test/capture_prop_gallery.gd @@ -1,11 +1,12 @@ extends SceneTree ## Dev helper: render every generated prop side by side on a plain backdrop and -## save user://screenshots/10-prop-gallery.png. The image feeds +## save user://screenshots/10-prop-gallery.png by default. The image feeds ## `tools/screen_designs.py compare` so a prop regression is visible. ## ## Usage: godot --path . --script res://src/test/capture_prop_gallery.gd +## godot --path . --script res://src/test/capture_prop_gallery.gd -- --out=res://docs/screenshots -const OUT_DIR := "user://screenshots" +var out_dir := "user://screenshots" const MODEL_DIR := "res://assets/models/generated/" const LIBRARY_DIR := "res://assets/models/blender/props/" const PROPS := ["bollard", "bench", "trash_bin", "planter_box", @@ -15,6 +16,9 @@ const SPACING := 1.9 var frames := 0 func _initialize() -> void: + for arg in OS.get_cmdline_user_args(): + if arg.begins_with("--out="): + out_dir = arg.trim_prefix("--out=") root.size = Vector2i(1280, 720) _build_stage() @@ -102,7 +106,7 @@ func _process(_delta: float) -> bool: if dir == null or not dir.dir_exists("screenshots"): dir.make_dir_recursive("screenshots") var image := root.get_texture().get_image() - var err := image.save_png(OUT_DIR.path_join("10-prop-gallery.png")) + var err := image.save_png(out_dir.path_join("10-prop-gallery.png")) print("10-prop-gallery.png -> err=%d (%dx%d)" % [err, image.get_width(), image.get_height()]) quit(0) return true diff --git a/src/test/capture_screenshots.gd b/src/test/capture_screenshots.gd index 18bea70..5afb1a0 100644 --- a/src/test/capture_screenshots.gd +++ b/src/test/capture_screenshots.gd @@ -1,10 +1,11 @@ extends SceneTree ## Dev helper: run the game windowed and capture staged screenshots for the ## README (day set + LATE-night set with street lamps and flashlight). -## Saves 1280x720 PNGs to user://screenshots/. +## Saves 1280x720 PNGs to user://screenshots/ by default. ## Usage: godot --path . --script res://src/test/capture_screenshots.gd +## godot --path . --script res://src/test/capture_screenshots.gd -- --out=res://docs/screenshots -const OUT_DIR := "user://screenshots" +var out_dir := "user://screenshots" const MAX_FRAMES := 3600 # hard cap (~60s) var frames := 0 @@ -16,6 +17,9 @@ var camera_staged := false var wand_attempts := 0 func _initialize() -> void: + for arg in OS.get_cmdline_user_args(): + if arg.begins_with("--out="): + out_dir = arg.trim_prefix("--out=") root.size = Vector2i(1280, 720) var packed: PackedScene = load("res://scenes/main.tscn") var inst: Node = packed.instantiate() @@ -231,9 +235,9 @@ func _face_target() -> void: gm.guard.head.rotation.x = deg_to_rad(-4.0) func _shoot(file_name: String) -> void: - var dir := DirAccess.open("user://") - if dir == null or not dir.dir_exists("screenshots"): - dir.make_dir_recursive("screenshots") + var dir := DirAccess.open(out_dir) + if dir == null: + DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(out_dir)) var img: Image = root.get_texture().get_image() - var err := img.save_png(OUT_DIR.path_join(file_name)) + var err := img.save_png(out_dir.path_join(file_name)) print("%s -> err=%d (%dx%d)" % [file_name, err, img.get_width(), img.get_height()]) diff --git a/tools/check_screenshots.sh b/tools/check_screenshots.sh new file mode 100755 index 0000000..745644c --- /dev/null +++ b/tools/check_screenshots.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Repeatable visual-regression smoke: render the staged captures into +# docs/screenshots and compare them against the runtime reference art. +# +# Runs under Xvfb automatically when no display is available. Requires Godot +# 4.3 on PATH (or GODOT=...), Python 3, Pillow, and xvfb-run for headless hosts. +set -euo pipefail +cd "$(dirname "$0")/.." + +GODOT="${GODOT:-godot}" +RUN=() +if [[ -z "${DISPLAY:-}" ]]; then + if ! command -v xvfb-run >/dev/null 2>&1; then + echo "error: no DISPLAY and xvfb-run is not installed" >&2 + exit 2 + fi + RUN=(xvfb-run -a) +fi + +"${RUN[@]}" "$GODOT" --path . --script res://src/test/capture_screenshots.gd -- --out=res://docs/screenshots +"${RUN[@]}" "$GODOT" --path . --script res://src/test/capture_prop_gallery.gd -- --out=res://docs/screenshots +python3 tools/screen_designs.py compare