Adopt the realism pass and refresh screenshots/docs
- add the v2 runtime picks: per-phase skyline panoramas, brick, sidewalk pavers, dry/wet asphalt, awning canvas, and their normal maps - add the realism tooling and evidence: tools/scene_concepts.py, capture_realism.gd, concept frames, reports/sheets, and tests - retune phase lighting/fog/exposure, add sun yaw, brighten the night glow, add the flashlight beam mesh, and rebuild the planter/tree assets - regenerate all eleven staged screenshots from the current build; compare is 11 good / 0 bad - finish the visual-alignment closeout in DONE/TODO, update architecture, blender-assets, PLAN, and ignore rules (editable Blender sources untracked) - keep the judge retry and document the verified default judge model Verification: headless 227/227; Python tooling 86/86; check_screenshots 11/11.
No files matched your search
@@ -66,3 +66,10 @@ assets/models/blender/**/*.png
|
|||||||
assets/models/blender_src/*.blend1
|
assets/models/blender_src/*.blend1
|
||||||
# Editable Blender sources are developer-only; keep the .gdignore marker tracked.
|
# Editable Blender sources are developer-only; keep the .gdignore marker tracked.
|
||||||
assets/models/blender_src/*.blend
|
assets/models/blender_src/*.blend
|
||||||
|
# Realism pass (tools/scene_concepts.py): the candidate pool and per-run captures
|
||||||
|
# stay local; the validated picks are tracked under game_ready/v2/picks/ (ADR-0014),
|
||||||
|
# and each run commits its report.{json,md} + sheet.jpg. Concept references are
|
||||||
|
# committed in docs/screens/concepts/.
|
||||||
|
assets/textures/ai/game_ready/v2/*
|
||||||
|
!assets/textures/ai/game_ready/v2/picks/
|
||||||
|
docs/screens/realism/**/*.png
|
||||||
@@ -895,6 +895,160 @@ Completed 2026-11-01. Historical note: the screen-design references originally l
|
|||||||
ADR-0014. `docs/screens/designs/` remains a reference-only snapshot; `tools/screen_designs.py`
|
ADR-0014. `docs/screens/designs/` remains a reference-only snapshot; `tools/screen_designs.py`
|
||||||
now reads and writes `assets/images/ui/`.
|
now reads and writes `assets/images/ui/`.
|
||||||
|
|
||||||
|
## Realistic Backgrounds, Textures and Scene References
|
||||||
|
|
||||||
|
Completed 2026-11-01 (part of the v0.6.x realism pass). The flat box skyline was replaced by
|
||||||
|
per-phase AI panoramas, the surface tiles by higher-detail v2 picks with normal maps, and the
|
||||||
|
lighting/exposure was retuned against AI concept frames. Evidence:
|
||||||
|
`docs/screens/realism/2026-11-01-adopt/` (`report.md`, `sheet.jpg`), `screen_designs.py compare`
|
||||||
|
**11 good / 0 bad**; headless **227/227**, Python **86/86**, all smokes.
|
||||||
|
|
||||||
|
### Realistic backgrounds, textures and scene references
|
||||||
|
|
||||||
|
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/<run>/`.
|
||||||
|
|
||||||
|
#### 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 `--swap=OLD=NEW` / `--tint=` / `--normal=` user arguments (the first version used
|
||||||
|
`--skyline=`/`--brick=`/… flags; `--swap` is path-based and also covers night stages). The
|
||||||
|
swap finds materials by their original texture path, so baseline and candidate frames use
|
||||||
|
the same camera. Writes to the `--out=` directory.
|
||||||
|
- [x] Capture the baseline (current assets) and the candidate set (v2 skyline + textures), and
|
||||||
|
copy them into `docs/screens/realism/<run>/{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
|
||||||
|
|
||||||
|
- [x] Fix sidewalk overexposure: lowered the day sun (0.78→0.62), exposure (0.8→0.6) and ambient
|
||||||
|
(0.42→0.22), and darkened the sidewalk/street albedo tints. The like-for-like ground
|
||||||
|
luminance in `docs/screens/realism/2026-11-01-adopt/` went from 173/162/132 to 95/92/73
|
||||||
|
(concept targets 78/100/100). The entrance shot stays ~20 % brighter than the concept: a
|
||||||
|
residual, tracked in the follow-ups below.
|
||||||
|
- [x] 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. Evening is
|
||||||
|
now dusk (`sun 0.3→0.12`, `exposure 0.95→0.72`, dark violet sky), Late is near-dark, and
|
||||||
|
`WorldBuilder.set_phase()` swaps the street to the wet asphalt map in Late.
|
||||||
|
- [x] Choose the skyline per phase (day → `skyline_day`, Evening → `skyline_dusk`, Late →
|
||||||
|
`skyline_night`) and stop the scene fog from bleaching it. The backdrop is now a
|
||||||
|
`fog_disabled` shader, because Godot 4.3 has no `fog_enabled` on `StandardMaterial3D`. The
|
||||||
|
first dusk panorama was a red floodlit skyline that clashed with the Evening sky, so a
|
||||||
|
calmer prompt produced `skyline_dusk-v5` (blue-grey towers, lit windows).
|
||||||
|
- [x] Raise the backdrop plane (60 m → 90 m) and use 2 UV repeats so the skyline is a band with
|
||||||
|
proportions near the 2:1 panorama, not a thin strip at the horizon.
|
||||||
|
- [x] Add facade depth (plinth, string courses, cornice, brick pilasters, window glazing bars)
|
||||||
|
and replace the blob trees and shrubs with denser foliage meshes: `_shrub()` now builds
|
||||||
|
many small flat-shaded clumps plus outer clumps that break the silhouette
|
||||||
|
(`street_tree` 1472→2592 tris), with fresh AO maps for `street_tree`, `planter_box` and
|
||||||
|
`round_planter`.
|
||||||
|
- [x] Re-cut `awning_canvas` so its stripes align at the tile edge: `stripe_period()` measures the
|
||||||
|
repeat and `align_stripes()` resamples to a whole number of periods (edge crossfade smeared
|
||||||
|
the stripes before). `audit_striped_tile()` checks the wrap step against the harshest
|
||||||
|
interior step instead of the flat edge-difference rule.
|
||||||
|
- [x] Capture shots from the Joke Shop and the studio too: `capture_realism.gd` now runs five
|
||||||
|
stages (day, evening, late, joke_shop, studio) with 9 shots and a `--stages=` subset. The
|
||||||
|
venues share the Hollywood geometry, so those two compare lighting, not layout.
|
||||||
|
|
||||||
|
#### Step 7 — Adopt the winners
|
||||||
|
|
||||||
|
- [x] Track the validated picks in place (ADR-0014). `.gitignore` now ignores the candidate pool
|
||||||
|
and un-ignores `game_ready/v2/picks/`, where `scene_concepts.py promote` copies the eight
|
||||||
|
validated candidates under stable names (`skyline_{day,dusk,night}`, `brick_facade`,
|
||||||
|
`sidewalk_pavers`, `asphalt`, `asphalt_wet`, `awning_canvas`) plus normal maps.
|
||||||
|
- [x] Point `WorldBuilder` at the chosen v2 skylines (separate day/dusk/night textures through
|
||||||
|
the new `set_phase()`), and retune the tints per phase.
|
||||||
|
- [x] Switch `materials.gd` / `world_builder.gd` surface preloads to the chosen v2 tiles + normal
|
||||||
|
maps, keeping the current UV scales (`Materials.asphalt()` gained a `wet` variant). Updated
|
||||||
|
`test_materials.gd` (pick paths, wet vs dry) and `test_world.gd` (shader backdrop, panorama
|
||||||
|
per phase, wet street).
|
||||||
|
- [x] Regenerate `docs/screenshots/` with `capture_screenshots.gd` (11/11), then run
|
||||||
|
`screen_designs.py compare` (**11 good / 0 bad**) and `judge` (5 match, 2 review, 4 diff —
|
||||||
|
the diffs are HUD/mockup layout gaps from the "Visual alignment" section, not realism
|
||||||
|
regressions; the default flash-lite judge model is currently failing with a provider 400, so
|
||||||
|
the run used `--model google/gemini-2.5-flash`). `scene_concepts.py realism` on
|
||||||
|
`docs/screens/realism/2026-11-01-adopt/` (3 judge repeats per shot) gives a like-for-like
|
||||||
|
improvement on every sub-score: overall 0.8→1.0, lighting 0.8→1.0, materials 0.8→1.0,
|
||||||
|
texture 0.8→1.0, backdrop 0.6→1.6, and the Pillow metric gap 0.444→0.348.
|
||||||
|
- [x] Update `README.md` (tool usage + gallery), `MEMORY.md` and `docs/architecture.md`.
|
||||||
|
|
||||||
## Visual Alignment — UI Paths and Art Loader
|
## Visual Alignment — UI Paths and Art Loader
|
||||||
|
|
||||||
Completed 2026-11-01.
|
Completed 2026-11-01.
|
||||||
@@ -950,3 +1104,26 @@ Completed 2026-11-01.
|
|||||||
## Improve in-game pop-ups
|
## Improve in-game pop-ups
|
||||||
|
|
||||||
- [x] Improve the visual appearance of in-game pop-ups (start/pause/settings/review/radio/wand/inspection art, buttons, palette, icons, text).
|
- [x] Improve the visual appearance of in-game pop-ups (start/pause/settings/review/radio/wand/inspection art, buttons, palette, icons, text).
|
||||||
|
|
||||||
|
## Visual Alignment With Rendered Mockups
|
||||||
|
|
||||||
|
Completed 2026-11-01. Every screen with an AI reference now loads or matches its mockup: the
|
||||||
|
runtime image root (ADR-0014), the UI/scene art loader, the menus/panels/HUD, and — last — the
|
||||||
|
scene lighting, fog and post-processing, which had been held until the realism pass landed.
|
||||||
|
`screen_designs.py compare` **11 good / 0 bad**; judge on the two scene shots: `01-venue-overview`
|
||||||
|
**match** (0.90), `05-late-night-avenue` **review** (0.10), 0 diff. Lighting: morning sun 34°
|
||||||
|
elevation with a phase `sun_yaw`, warm day haze (`fog_color` 0.87/0.83/0.76, density 0.0034,
|
||||||
|
energy 0.5), stronger night glow, street albedo 0.62. Remaining differences (camera height,
|
||||||
|
marquee text, hand-authored tree shapes) are content, not grading.
|
||||||
|
|
||||||
|
### Visual alignment with rendered mockups
|
||||||
|
|
||||||
|
#### Align in-game visuals
|
||||||
|
|
||||||
|
- [x] Make the venue selection screen match the `start_menu` mockup (layout, palette, background).
|
||||||
|
- [x] Make the HUD match the `targeting_npc` / `escalation_hud` mockups (focus ring, escalation meter, log, hints).
|
||||||
|
- [x] Align the pause, settings, and shift-review panels to their mockups.
|
||||||
|
- [x] Align the radio and wand panels to their mockups.
|
||||||
|
- [x] Align the body-camera timeline to its mockup.
|
||||||
|
- [x] Align scene lighting, fog, and post-processing for `venue_overview` and `late_night_avenue`: lowered the morning sun to 34° (`sun_yaw` is now a phase value), warmed and thickened the day haze, raised the night glow, and brightened the street albedo. `judge` moved `01-venue-overview` to **match** (0.90) and `05-late-night-avenue` from diff to **review** (0.10); `compare` stays 11 good / 0 bad.
|
||||||
|
- [x] Fix the body-camera REC/clock overlay overlapping the shift/rep/crowd status in the top-right HUD.
|
||||||
@@ -420,6 +420,42 @@ frame budget measured by `smoke_npc_budget.gd`. See
|
|||||||
[`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md) and
|
[`docs/plan-graphics-v0.6.md`](docs/plan-graphics-v0.6.md) and
|
||||||
[ADR-0013](docs/adr/0013-blender-authored-npc-characters.md).
|
[ADR-0013](docs/adr/0013-blender-authored-npc-characters.md).
|
||||||
|
|
||||||
|
### Realism pass
|
||||||
|
|
||||||
|
The target look is the realistic 3D-game frames in `docs/screens/designs/` and the larger
|
||||||
|
example set in `docs/screens/concepts/`: hazy, layered city depth, believable materials, and
|
||||||
|
warm practical lights at night. Readability still comes first. Silhouettes, the focus ring and
|
||||||
|
the HUD must stay clear, and the GL Compatibility renderer stays.
|
||||||
|
|
||||||
|
- **Backgrounds.** The flat, black-box skyline strip is retired in favour of AI-generated
|
||||||
|
panoramic skylines (Hollywood mid-rises, palms, hills) for day, dusk and night. Each one is a
|
||||||
|
transparent-sky 4:1 strip that tiles horizontally on the existing backdrop ring, and the night
|
||||||
|
variant has lit windows.
|
||||||
|
- **Textures.** Higher-detail seamless 1024² tiles with derived normal maps for brick, granite,
|
||||||
|
sidewalk pavers, asphalt (dry and wet), steps, awning canvas and stucco, at the current UV
|
||||||
|
scales.
|
||||||
|
- **Process.** `tools/scene_concepts.py` generates (never overwriting existing images),
|
||||||
|
processes, audits and judges. `src/test/capture_realism.gd` captures the same camera shots
|
||||||
|
with the current and the candidate assets, swapped at runtime, so a candidate is compared
|
||||||
|
before any game code changes. Candidates are adopted only when the realism report (Pillow
|
||||||
|
detail metrics + vision-model realism scores) improves and `screen_designs.py judge` shows no
|
||||||
|
new `diff`.
|
||||||
|
- **Storage.** Raw AI output: `assets/textures/ai/v2/raw/` (local, gitignored). Game-ready
|
||||||
|
candidates: `assets/textures/ai/game_ready/v2/`. Scene references: `docs/screens/concepts/`.
|
||||||
|
Trial captures and reports: `docs/screens/realism/<run>/`.
|
||||||
|
- **First result (2026-11-01).** Swapping in the v2 skyline and tiles raised the judge's mean
|
||||||
|
backdrop score from 2.6 to 4.0 and texture detail from 1.2 to ~1.7. Overall realism stayed at
|
||||||
|
~2/10; the limits were lighting and exposure, flat facades and blob foliage, not textures.
|
||||||
|
- **Adopted (2026-11-01).** The v2 picks are now the runtime skyline and surface set, the phase
|
||||||
|
lighting was retuned (exposure, darker dusk/late, practical lights, wet asphalt at night), the
|
||||||
|
backdrop is a fog-free 90 m panorama ring per phase, the facade gained plinth/string
|
||||||
|
courses/cornice/pilasters/glazing bars, and the library trees and shrubs are denser. Judge
|
||||||
|
scores on the like-for-like shots rose on every sub-score (overall 0.8 → 1.0, backdrop
|
||||||
|
0.6 → 1.6) with the metric gap down 0.444 → 0.348
|
||||||
|
(`docs/screens/realism/2026-11-01-adopt/`). The remaining gap to the concepts is the low-poly
|
||||||
|
character and prop fidelity, a visible flashlight beam at night, and the entrance still being
|
||||||
|
~20 % brighter than the concept (`TODO.md` → Realism follow-ups).
|
||||||
|
|
||||||
The scenario should test the player’s ability to distinguish ordinary Hollywood Boulevard activity from a genuine security problem. The best solution is usually a simple gesture, clear direction, or credential check rather than immediate escalation.
|
The scenario should test the player’s ability to distinguish ordinary Hollywood Boulevard activity from a genuine security problem. The best solution is usually a simple gesture, clear direction, or credential check rather than immediate escalation.
|
||||||
|
|
||||||
Future venues can reuse this structure while changing the rules, equipment, staff roles, and types of access decisions.
|
Future venues can reuse this structure while changing the rules, equipment, staff roles, and types of access decisions.
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
## Visual alignment with rendered mockups
|
|
||||||
|
|
||||||
### Align in-game visuals
|
|
||||||
|
|
||||||
- [x] Make the venue selection screen match the `start_menu` mockup (layout, palette, background).
|
|
||||||
- [x] Make the HUD match the `targeting_npc` / `escalation_hud` mockups (focus ring, escalation meter, log, hints).
|
|
||||||
- [x] Align the pause, settings, and shift-review panels to their mockups.
|
|
||||||
- [x] Align the radio and wand panels to their mockups.
|
|
||||||
- [x] Align the body-camera timeline to its mockup.
|
|
||||||
- [ ] Align scene lighting, fog, and post-processing for `venue_overview` and `late_night_avenue` (blocked: the active realism pass is editing `materials.gd`/`world_builder.gd` and the captures; see `MEMORY.md`).
|
|
||||||
- [x] Fix the body-camera REC/clock overlay overlapping the shift/rep/crowd status in the top-right HUD.
|
|
||||||
|
|
||||||
## Realism follow-ups (v0.6.1 candidates)
|
## Realism follow-ups (v0.6.1 candidates)
|
||||||
|
|
||||||
- [ ] Fixed ground exposure for the entrance view: it is still ~20 % brighter than the concept.
|
- [x] Fixed ground exposure for the entrance view: the morning sun now sits at 34° elevation
|
||||||
Either put the entrance in the building's shadow (sun yaw/elevation) or add a slight
|
instead of 50°, so the facade shades the near sidewalk and props cast long shadows. Ground
|
||||||
per-view exposure so the shaded sidewalk reads mid-grey.
|
luminance went 95 → **83** against the concept's 78 (it was 173 before the realism pass),
|
||||||
- [ ] Make the flashlight read in the dark: the `06-night-flashlight` concept shows a visible
|
with the boulevard at 82 and the street at 81.
|
||||||
|
- [-] 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
|
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.
|
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).
|
- [ ] NPC skin/clothing still reads as low-poly next to the concepts (judge `characters` ≈ 0–1).
|
||||||
|
|||||||
@@ -836,8 +836,8 @@
|
|||||||
],
|
],
|
||||||
"path": "assets/models/blender/props/planter_box.glb",
|
"path": "assets/models/blender/props/planter_box.glb",
|
||||||
"replaces": "generated/planter_box.glb",
|
"replaces": "generated/planter_box.glb",
|
||||||
"triangles": 924,
|
"triangles": 1324,
|
||||||
"vertices": 874
|
"vertices": 2780
|
||||||
},
|
},
|
||||||
"protest_sign": {
|
"protest_sign": {
|
||||||
"attach": "hand",
|
"attach": "hand",
|
||||||
@@ -892,14 +892,14 @@
|
|||||||
"round_planter": {
|
"round_planter": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
0.394,
|
0.432,
|
||||||
1.12,
|
1.12,
|
||||||
0.375
|
0.36
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.39,
|
-0.36,
|
||||||
0,
|
0,
|
||||||
-0.38
|
-0.363
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"category": "props",
|
"category": "props",
|
||||||
@@ -908,8 +908,8 @@
|
|||||||
"foliage"
|
"foliage"
|
||||||
],
|
],
|
||||||
"path": "assets/models/blender/props/round_planter.glb",
|
"path": "assets/models/blender/props/round_planter.glb",
|
||||||
"triangles": 920,
|
"triangles": 1240,
|
||||||
"vertices": 896
|
"vertices": 2418
|
||||||
},
|
},
|
||||||
"scba_tank": {
|
"scba_tank": {
|
||||||
"attach": "back",
|
"attach": "back",
|
||||||
@@ -1063,14 +1063,14 @@
|
|||||||
"street_tree": {
|
"street_tree": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
1.019,
|
1.179,
|
||||||
3.65,
|
3.62,
|
||||||
1.117
|
1.102
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-1.077,
|
-0.941,
|
||||||
0,
|
0,
|
||||||
-1.016
|
-1.082
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"category": "props",
|
"category": "props",
|
||||||
@@ -1082,8 +1082,8 @@
|
|||||||
],
|
],
|
||||||
"path": "assets/models/blender/props/street_tree.glb",
|
"path": "assets/models/blender/props/street_tree.glb",
|
||||||
"replaces": "WorldBuilder cone trees",
|
"replaces": "WorldBuilder cone trees",
|
||||||
"triangles": 1472,
|
"triangles": 2592,
|
||||||
"vertices": 1396
|
"vertices": 5620
|
||||||
},
|
},
|
||||||
"sunglasses": {
|
"sunglasses": {
|
||||||
"attach": "head",
|
"attach": "head",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
@@ -81,7 +81,7 @@ The player interacts with NPCs, venue entrances, access artifacts, equipment, st
|
|||||||
- `tools/model_pipeline.py` builds and audits the street-prop `.glb` models and packs their colour atlases.
|
- `tools/model_pipeline.py` builds and audits the street-prop `.glb` models and packs their colour atlases.
|
||||||
- `tools/npc_pipeline.py` builds and audits the skinned NPC base model (`assets/models/generated/npc_base.glb`) and its per-slot material maps, with an optional AI fabric override (ADR-0013).
|
- `tools/npc_pipeline.py` builds and audits the skinned NPC base model (`assets/models/generated/npc_base.glb`) and its per-slot material maps, with an optional AI fabric override (ADR-0013).
|
||||||
- `tools/blender_assets.py` builds, renders and audits the Blender asset library (`assets/models/blender/{npc,npc_gear,props}/` + `manifest.json`): NPC character variants that are drop-in for `npc_base`, attach-point gear, and street/venue props. `NpcAvatar` selects variants/gear by role/seed and `WorldBuilder` loads the prop replacements and new street dressing, with procedural fallbacks (see `docs/blender-assets.md`).
|
- `tools/blender_assets.py` builds, renders and audits the Blender asset library (`assets/models/blender/{npc,npc_gear,props}/` + `manifest.json`): NPC character variants that are drop-in for `npc_base`, attach-point gear, and street/venue props. `NpcAvatar` selects variants/gear by role/seed and `WorldBuilder` loads the prop replacements and new street dressing, with procedural fallbacks (see `docs/blender-assets.md`).
|
||||||
- `tools/scene_concepts.py` generates realism-pass references (`docs/screens/concepts/`), skyline backdrops and surface tiles (raw `assets/textures/ai/v2/raw/` → candidates `assets/textures/ai/game_ready/v2/`) without ever overwriting an image, and scores `src/test/capture_realism.gd` A/B captures against the concepts (`docs/screens/realism/<run>/`).
|
- `tools/scene_concepts.py` generates realism-pass references (`docs/screens/concepts/`), skyline backdrops and surface tiles (raw `assets/textures/ai/v2/raw/` → candidates `assets/textures/ai/game_ready/v2/`), promotes the validated ones to the tracked `v2/picks/` that `SurfaceMaterials`/`WorldBuilder` preload, audits them, and scores `src/test/capture_realism.gd` A/B captures (five stages, runtime texture swaps) against the concepts (`docs/screens/realism/<run>/`).
|
||||||
- `tools/asset_pipeline.py` generates and audits role-aware textures; `tools/screen_designs.py` generates screen references and judges captures.
|
- `tools/asset_pipeline.py` generates and audits role-aware textures; `tools/screen_designs.py` generates screen references and judges captures.
|
||||||
|
|
||||||
Confirmed game assets live in `assets/textures/ai/game_ready/` (audited surface, UI, and card textures), `assets/models/generated/` (prop and NPC `.glb` models plus their texture atlases), and `assets/textures/npc/` (NPC material maps and `palette.json`). Development-only outputs — AI concept sheets, Blender sources, raw AI fabrics — are gitignored and regenerated by the tools.
|
Confirmed game assets live in `assets/textures/ai/game_ready/` (audited surface, UI, and card textures), `assets/models/generated/` (prop and NPC `.glb` models plus their texture atlases), and `assets/textures/npc/` (NPC material maps and `palette.json`). Development-only outputs — AI concept sheets, Blender sources, raw AI fabrics — are gitignored and regenerated by the tools.
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ render about 2.5 min.
|
|||||||
- Blender is Z-up. The glTF exporter converts to Godot Y-up.
|
- Blender is Z-up. The glTF exporter converts to Godot Y-up.
|
||||||
- **Front = Blender −Y = Godot +Z**, the face direction that
|
- **Front = Blender −Y = Godot +Z**, the face direction that
|
||||||
`NpcAvatar.yaw_towards()` assumes.
|
`NpcAvatar.yaw_towards()` assumes.
|
||||||
|
- **Foliage** (`street_tree`, `planter_box`, `round_planter`) is a cluster of many small
|
||||||
|
flat-shaded clumps plus outer clumps that break the silhouette, not one smooth blob. That is
|
||||||
|
how the realism pass made the street greenery read as leaves; the three assets carry fresh AO
|
||||||
|
maps (`street_tree` is 2,592 triangles).
|
||||||
- Grounded assets have their origin on the floor (`min y ≈ 0`). The two
|
- Grounded assets have their origin on the floor (`min y ≈ 0`). The two
|
||||||
exceptions are `wall_lantern` (origin on the wall plate, at mount height) and
|
exceptions are `wall_lantern` (origin on the wall plate, at mount height) and
|
||||||
`marquee` (origin at the wall where the canopy meets it, at canopy height).
|
`marquee` (origin at the wall where the canopy meets it, at canopy height).
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Scene concepts: the realistic target look
|
||||||
|
|
||||||
|
These are AI-generated example frames (`python3 tools/scene_concepts.py generate --group scene`,
|
||||||
|
`openai/gpt-5-image-mini`, 1280×720) for the realism pass. They are art direction, not
|
||||||
|
assets. `tools/scene_concepts.py realism` pairs them with the in-game shots from
|
||||||
|
`src/test/capture_realism.gd` (see `SHOTS` in the tool). New generations never overwrite a
|
||||||
|
file here; they are saved as `<name>-vN.png`.
|
||||||
|
|
||||||
|
| Concept | Visual targets it sets |
|
||||||
|
| --- | --- |
|
||||||
|
| `hollywood_entrance_morning` | warm low sun, long soft shadows, brick with visible mortar and wear, stone plinth, potted topiary, haze that fades the far street to beige |
|
||||||
|
| `hollywood_entrance_golden_hour` | strong back light and rim light on people, marquee bulbs reading even in daylight |
|
||||||
|
| `hollywood_entrance_rain_night` | dark scene lit by practical lights only (marquee, sodium lamps), wet glossy ground with reflections, fog |
|
||||||
|
| `boulevard_skyline_day` | deep layered skyline with real towers and haze, palms lining the road, red curb paint, believable asphalt |
|
||||||
|
| `boulevard_skyline_dusk` | purple/orange gradient sky, neon signs, lit windows, car light trails |
|
||||||
|
| `joke_shop_alley_evening` | narrow brick alley, one warm wall lantern as key light, deep shadows, people in dark jackets |
|
||||||
|
| `joke_shop_door_closeup` | close material detail: brick, mortar, aged wood door, brass |
|
||||||
|
| `studio_stage_door_night` | granite facade, a bright bulb canopy as the main light, a glossy black car, stanchions and a barrier |
|
||||||
|
| `sidewalk_material_closeup` | paver joints, stains, granite curb, aggregate asphalt and a tree grate at grazing light |
|
||||||
|
| `entrance_steps_podium` | granite steps, handrails, glass doors, the check-in podium |
|
||||||
|
| `crowd_barrier_line` | steel barriers, a red carpet, a crowd with depth-of-field haze |
|
||||||
|
| `responder_arrival_wide` | fire engine, turnout gear, red/blue light spill on wet pavement |
|
||||||
|
|
||||||
|
Cross-cutting targets, for comparison with the 2026-11-01 captures in
|
||||||
|
`docs/screens/realism/2026-11-01-v2/sheet.jpg`:
|
||||||
|
|
||||||
|
- **Exposure.** The concepts keep the ground mid-grey. The game's sidewalk is close to white,
|
||||||
|
so it is overexposed.
|
||||||
|
- **Night means dark.** The night/dusk concepts are lit by practical lights. The game's
|
||||||
|
"Evening" phase is a bright pink-orange frame.
|
||||||
|
- **Depth.** The concepts show a layered skyline fading into haze. The new v2 skyline strips
|
||||||
|
supply this; the old box skyline did not.
|
||||||
|
- **Geometry and foliage.** The facades have plinths, cornices and deep window reveals, and the
|
||||||
|
trees and shrubs are organic. The game uses flat walls and blob trees.
|
||||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,878 @@
|
|||||||
|
{
|
||||||
|
"run": "docs/screens/realism/2026-11-01-adopt",
|
||||||
|
"judge_model": "google/gemini-2.5-flash",
|
||||||
|
"sets": {
|
||||||
|
"after": {
|
||||||
|
"boulevard_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 23.21,
|
||||||
|
"fine_detail": 8.88,
|
||||||
|
"contrast": 38.78,
|
||||||
|
"saturation_mean": 99.58,
|
||||||
|
"colour_spread": 46.73
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.172,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
3.0,
|
||||||
|
3.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement physically based rendering (PBR) materials for all assets, focusing on realistic albedo, roughness, and metallic maps to achieve accurate surface responses to light.",
|
||||||
|
"Overhaul lighting system to include global illumination, realistic skybox/HDRI for ambient light, and accurate sun/directional light with proper shadows and atmospheric scattering (haze/fog).",
|
||||||
|
"Replace placeholder low-polygon models with high-detail 3D assets for trees, street furniture, and buildings, ensuring proper UV mapping and texture application.",
|
||||||
|
"Improve texture resolution and detail across all surfaces, adding normal maps for surface imperfections and detail, and ensuring textures are seamlessly tiled or uniquely mapped where appropriate.",
|
||||||
|
"Develop a realistic character pipeline, including high-fidelity models, animations, and PBR materials for skin, clothing, and hair, to populate the scene."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boulevard_night": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 16.02,
|
||||||
|
"fine_detail": 7.13,
|
||||||
|
"contrast": 39.4,
|
||||||
|
"saturation_mean": 163.01,
|
||||||
|
"colour_spread": 40.42
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.43,
|
||||||
|
"fine_detail": 3.51,
|
||||||
|
"contrast": 27.34,
|
||||||
|
"saturation_mean": 159.44,
|
||||||
|
"colour_spread": 26.61
|
||||||
|
},
|
||||||
|
"metric_gap": 0.723,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 2.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
2.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a physically based rendering (PBR) material pipeline for all assets to achieve realistic surface responses to light, especially for wet surfaces and brick.",
|
||||||
|
"Overhaul the lighting system to include global illumination, volumetric fog/rain, and realistic light sources with proper falloff and color temperature, focusing on dramatic contrast and mood.",
|
||||||
|
"Replace low-polygon models with high-detail meshes and high-resolution textures, including normal and roughness maps, for all environmental elements (buildings, street, props, characters).",
|
||||||
|
"Redesign the backdrop with a more detailed and atmospheric skyline, incorporating distance haze, subtle cloud formations, and appropriate night sky colors to match the target's mood.",
|
||||||
|
"Update character models to be realistic, with detailed clothing, skin, and hair textures, and implement realistic animation and shading."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrance_day": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_morning.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 25.88,
|
||||||
|
"fine_detail": 9.86,
|
||||||
|
"contrast": 40.18,
|
||||||
|
"saturation_mean": 93.04,
|
||||||
|
"colour_spread": 43.24
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 16.87,
|
||||||
|
"fine_detail": 6.92,
|
||||||
|
"contrast": 70.27,
|
||||||
|
"saturation_mean": 121.02,
|
||||||
|
"colour_spread": 69.83
|
||||||
|
},
|
||||||
|
"metric_gap": 0.442,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 0.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a physically based rendering (PBR) material pipeline for all assets to achieve realistic surface responses to light.",
|
||||||
|
"Overhaul the lighting system to include global illumination, realistic skyboxes with atmospheric scattering, and dynamic shadows.",
|
||||||
|
"Replace placeholder low-polygon models with high-fidelity assets that include detailed normal maps and displacement maps for architectural elements, foliage, and street furniture.",
|
||||||
|
"Develop a detailed backdrop with distant buildings, varied skyline, and atmospheric haze to create depth and realism.",
|
||||||
|
"Add realistic foliage models with proper leaf textures, branch structures, and varied forms instead of simple geometric shapes."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrance_late": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 18.4,
|
||||||
|
"fine_detail": 8.41,
|
||||||
|
"contrast": 51.61,
|
||||||
|
"saturation_mean": 127.4,
|
||||||
|
"colour_spread": 51.96
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.43,
|
||||||
|
"fine_detail": 3.51,
|
||||||
|
"contrast": 27.34,
|
||||||
|
"saturation_mean": 159.44,
|
||||||
|
"colour_spread": 26.61
|
||||||
|
},
|
||||||
|
"metric_gap": 1.105,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 0.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 1.33,
|
||||||
|
"materials": 1.33
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
2.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement volumetric fog and rain effects to create atmospheric depth and mood, especially around light sources.",
|
||||||
|
"Overhaul lighting system to include realistic light falloff, bounce light, and wet surface reflections, focusing on the marquee and streetlights.",
|
||||||
|
"Redesign all materials with PBR workflows, ensuring accurate roughness, metallic, and normal maps for brick, concrete, metal, and wet surfaces.",
|
||||||
|
"Add high-resolution, detailed textures for all surfaces, particularly the brickwork, sidewalk, and marquee, to capture fine imperfections and wear.",
|
||||||
|
"Develop a detailed backdrop with distant buildings, subtle city lights, and atmospheric haze to replace the current flat sky."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"joke_shop_door": {
|
||||||
|
"target": "docs/screens/concepts/joke_shop_door_closeup.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 14.84,
|
||||||
|
"fine_detail": 7.04,
|
||||||
|
"contrast": 47.63,
|
||||||
|
"saturation_mean": 132.89,
|
||||||
|
"colour_spread": 49.43
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 9.79,
|
||||||
|
"fine_detail": 3.59,
|
||||||
|
"contrast": 26.09,
|
||||||
|
"saturation_mean": 117.17,
|
||||||
|
"colour_spread": 25.09
|
||||||
|
},
|
||||||
|
"metric_gap": 0.818,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 2.0,
|
||||||
|
"backdrop": 4.0,
|
||||||
|
"lighting": 2.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 3.0,
|
||||||
|
"materials": 3.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
2.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
4.0,
|
||||||
|
4.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
2.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
3.0,
|
||||||
|
3.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
3.0,
|
||||||
|
3.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement physically based rendering (PBR) materials for all surfaces, especially brick, wood, and metal, to achieve realistic light interaction and surface properties.",
|
||||||
|
"Overhaul the lighting system to include more nuanced light sources, volumetric effects, and global illumination. Focus on creating warm, inviting pools of light around entrances and signs, with realistic falloff and shadows.",
|
||||||
|
"Improve texture resolution and detail for all assets, particularly the brickwork, ground, and signage. Add normal maps and displacement maps to give surfaces depth and tactile quality.",
|
||||||
|
"Redesign the 'Comedy Club' sign to be a prominent, well-lit feature with appropriate emissive materials and a more stylized font, similar to the target image's 'COMEDY CLUB' sign.",
|
||||||
|
"Enhance the backdrop with more detailed city lights, atmospheric haze, and a more realistic skybox to create a sense of depth and a believable urban environment."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 18.69,
|
||||||
|
"fine_detail": 7.42,
|
||||||
|
"contrast": 42.69,
|
||||||
|
"saturation_mean": 106.86,
|
||||||
|
"colour_spread": 48.19
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.148,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 2.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
2.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement physically based rendering (PBR) materials for all assets, focusing on realistic albedo, roughness, and metallic values to achieve accurate light interaction.",
|
||||||
|
"Overhaul the lighting system to include a directional light representing the sun, a skybox/HDRI for ambient light, and global illumination to simulate bounced light, matching the target's clear, bright, and slightly warm daylight.",
|
||||||
|
"Replace placeholder geometry with high-fidelity 3D models for cars, buildings, and environmental elements (e.g., palm trees, streetlights, sidewalks) that match the realistic proportions and details of the target image.",
|
||||||
|
"Develop detailed textures for all surfaces, including road asphalt, concrete sidewalks, building facades, and foliage, ensuring appropriate resolution and variety to avoid tiling artifacts and enhance realism.",
|
||||||
|
"Create a realistic backdrop with a detailed skyline, atmospheric perspective (distance haze), and a clear blue sky that accurately reflects the target's Los Angeles aesthetic, integrating it seamlessly with the foreground."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_late": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 12.95,
|
||||||
|
"fine_detail": 6.27,
|
||||||
|
"contrast": 31.94,
|
||||||
|
"saturation_mean": 122.75,
|
||||||
|
"colour_spread": 36.44
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.83,
|
||||||
|
"fine_detail": 4.61,
|
||||||
|
"contrast": 33.26,
|
||||||
|
"saturation_mean": 188.16,
|
||||||
|
"colour_spread": 35.38
|
||||||
|
},
|
||||||
|
"metric_gap": 0.224,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 0.0,
|
||||||
|
"backdrop": 0.0,
|
||||||
|
"lighting": 0.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 0.0,
|
||||||
|
"materials": 0.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a full physically-based lighting model with global illumination, volumetric fog, and atmospheric scattering to achieve realistic sunset colors and light interaction.",
|
||||||
|
"Replace placeholder geometry with detailed 3D models for buildings, palm trees, and vehicles, ensuring proper scale and architectural variety.",
|
||||||
|
"Develop high-resolution PBR (Physically Based Rendering) materials for all surfaces, including reflective wet asphalt, glowing neon signs, and varied building facades, with appropriate roughness, metallic, and normal maps.",
|
||||||
|
"Create a dynamic skybox system that accurately renders sunset gradients, distant city skylines with emissive windows, and atmospheric haze, replacing the current flat backdrop.",
|
||||||
|
"Add realistic character models with appropriate clothing and animations to populate the scene, contributing to the overall realism and sense of a living city."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_night": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 10.97,
|
||||||
|
"fine_detail": 5.57,
|
||||||
|
"contrast": 24.27,
|
||||||
|
"saturation_mean": 190.35,
|
||||||
|
"colour_spread": 25.15
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.83,
|
||||||
|
"fine_detail": 4.61,
|
||||||
|
"contrast": 33.26,
|
||||||
|
"saturation_mean": 188.16,
|
||||||
|
"colour_spread": 35.38
|
||||||
|
},
|
||||||
|
"metric_gap": 0.253,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 1.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a full physically-based lighting model with global illumination, volumetric fog, and proper emissive materials for signs and car lights to capture the sunset glow and light trails.",
|
||||||
|
"Replace placeholder geometry with detailed 3D models for buildings, palm trees, and vehicles, ensuring accurate proportions and architectural details.",
|
||||||
|
"Develop high-resolution PBR textures for all surfaces (roads, buildings, signs, vehicles) with appropriate albedo, roughness, metallic, and normal maps to add realism and detail.",
|
||||||
|
"Create a dynamic skybox with a realistic sunset gradient, distant city skyline with illuminated windows, and atmospheric haze to match the target's depth and mood.",
|
||||||
|
"Add realistic character models with appropriate clothing and animations to populate the scene and enhance the sense of a living city."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"studio_door": {
|
||||||
|
"target": "docs/screens/concepts/studio_stage_door_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 16.62,
|
||||||
|
"fine_detail": 7.65,
|
||||||
|
"contrast": 48.52,
|
||||||
|
"saturation_mean": 133.55,
|
||||||
|
"colour_spread": 49.81
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 11.39,
|
||||||
|
"fine_detail": 4.87,
|
||||||
|
"contrast": 39.17,
|
||||||
|
"saturation_mean": 104.49,
|
||||||
|
"colour_spread": 38.0
|
||||||
|
},
|
||||||
|
"metric_gap": 0.395,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 0.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement physically based rendering (PBR) materials for all surfaces, focusing on realistic albedo, roughness, and metallic values to achieve accurate light interaction.",
|
||||||
|
"Overhaul the lighting system to include global illumination, volumetric fog, and realistic light sources with proper falloff and color temperature, especially for the marquee and wall sconces.",
|
||||||
|
"Add high-resolution, detailed textures with normal and displacement maps for brickwork, concrete, and metallic elements to introduce surface imperfections and depth.",
|
||||||
|
"Develop a realistic backdrop with a distant city skyline, atmospheric haze, and a dark, moody sky to establish a sense of depth and environment.",
|
||||||
|
"Introduce realistic 3D character models with proper clothing, hair, and skin shaders, along with a high-fidelity car model, to populate the scene and match the target's visual complexity."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"before": {
|
||||||
|
"boulevard_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 20.75,
|
||||||
|
"fine_detail": 8.2,
|
||||||
|
"contrast": 49.63,
|
||||||
|
"saturation_mean": 72.04,
|
||||||
|
"colour_spread": 55.45
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.036,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 1.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a physically based rendering (PBR) material system for all assets to achieve realistic surface interactions with light.",
|
||||||
|
"Integrate a global illumination solution (e.g., ray tracing, baked lightmaps) to simulate realistic light bouncing and ambient occlusion.",
|
||||||
|
"Replace placeholder low-resolution textures with high-resolution, detailed textures, including normal maps and roughness maps for all surfaces.",
|
||||||
|
"Develop a realistic skybox and distant cityscape with atmospheric scattering and haze effects to match the target's backdrop.",
|
||||||
|
"Redesign character models with realistic proportions, detailed clothing, and proper skin shaders, along with animation systems for natural movement."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boulevard_night": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 21.77,
|
||||||
|
"fine_detail": 8.45,
|
||||||
|
"contrast": 50.38,
|
||||||
|
"saturation_mean": 95.12,
|
||||||
|
"colour_spread": 55.61
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.43,
|
||||||
|
"fine_detail": 3.51,
|
||||||
|
"contrast": 27.34,
|
||||||
|
"saturation_mean": 159.44,
|
||||||
|
"colour_spread": 26.61
|
||||||
|
},
|
||||||
|
"metric_gap": 1.231,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 1.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a physically based rendering (PBR) material pipeline for all assets to achieve realistic surface responses to light.",
|
||||||
|
"Overhaul the lighting system to include global illumination, volumetric effects (fog/haze), and realistic light sources with proper falloff and color temperature.",
|
||||||
|
"Redesign character models with higher polygon counts, detailed textures (albedo, normal, roughness, metallic maps), and realistic rigging/animation.",
|
||||||
|
"Replace placeholder textures with high-resolution, detailed textures that include normal maps for surface imperfections and depth.",
|
||||||
|
"Develop a more complex backdrop with detailed buildings, atmospheric perspective (distance haze), and a dynamic sky system that reflects time of day and weather."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrance_day": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_morning.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 26.19,
|
||||||
|
"fine_detail": 9.27,
|
||||||
|
"contrast": 56.39,
|
||||||
|
"saturation_mean": 65.24,
|
||||||
|
"colour_spread": 59.96
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 16.87,
|
||||||
|
"fine_detail": 6.92,
|
||||||
|
"contrast": 70.27,
|
||||||
|
"saturation_mean": 121.02,
|
||||||
|
"colour_spread": 69.83
|
||||||
|
},
|
||||||
|
"metric_gap": 0.308,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 0.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a physically based rendering (PBR) material pipeline for all assets to achieve realistic surface interactions with light.",
|
||||||
|
"Overhaul the lighting system to include global illumination, realistic skyboxes with atmospheric scattering, and dynamic shadows to match the soft, warm lighting of the target.",
|
||||||
|
"Replace placeholder textures with high-resolution, detailed textures that include normal maps, roughness maps, and metallic maps to add surface complexity and realism.",
|
||||||
|
"Develop a detailed backdrop with distant buildings, realistic palm trees, and atmospheric haze to create depth and a sense of place.",
|
||||||
|
"Redesign all environmental props (trees, trash cans, bollards) with realistic models and textures, moving away from the current stylized, low-poly aesthetic."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 12.44,
|
||||||
|
"fine_detail": 5.32,
|
||||||
|
"contrast": 41.37,
|
||||||
|
"saturation_mean": 77.84,
|
||||||
|
"colour_spread": 44.96
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.307,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 0.0,
|
||||||
|
"backdrop": 0.0,
|
||||||
|
"lighting": 0.0,
|
||||||
|
"characters": 0.0,
|
||||||
|
"texture_detail": 0.0,
|
||||||
|
"materials": 0.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a physically based rendering (PBR) material system for all assets to achieve realistic surface interactions with light.",
|
||||||
|
"Develop a dynamic sky and atmospheric scattering system to accurately simulate sky color, haze, and sun position based on time of day.",
|
||||||
|
"Replace placeholder block models with detailed 3D models for buildings, vehicles, and environmental props, including proper UV mapping and high-resolution textures.",
|
||||||
|
"Integrate a global illumination solution (e.g., ray tracing or baked lightmaps) to simulate realistic indirect lighting and soft shadows.",
|
||||||
|
"Create realistic character models with detailed textures, proper rigging, and animations, ensuring they blend seamlessly with the environment."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_night": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 11.94,
|
||||||
|
"fine_detail": 5.35,
|
||||||
|
"contrast": 48.34,
|
||||||
|
"saturation_mean": 98.21,
|
||||||
|
"colour_spread": 48.85
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.83,
|
||||||
|
"fine_detail": 4.61,
|
||||||
|
"contrast": 33.26,
|
||||||
|
"saturation_mean": 188.16,
|
||||||
|
"colour_spread": 35.38
|
||||||
|
},
|
||||||
|
"metric_gap": 0.337,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"overall": 1.0,
|
||||||
|
"backdrop": 1.0,
|
||||||
|
"lighting": 1.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"materials": 1.0
|
||||||
|
},
|
||||||
|
"score_range": {
|
||||||
|
"overall": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"backdrop": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"lighting": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"characters": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"texture_detail": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"materials": [
|
||||||
|
1.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"runs": 3,
|
||||||
|
"fixes": [
|
||||||
|
"Implement a physically based lighting model with global illumination to accurately simulate light bounces and color bleeding, especially for the sunset and artificial lights.",
|
||||||
|
"Replace placeholder geometry with detailed 3D models for buildings, vehicles, and environmental props, ensuring proper UV mapping and high-resolution textures.",
|
||||||
|
"Develop a comprehensive material library with realistic PBR (Physically Based Rendering) materials for various surfaces (e.g., wet asphalt, neon signs, building facades, car paint) including roughness, metallic, and normal maps.",
|
||||||
|
"Create a dynamic skybox system that accurately represents a sunset with atmospheric scattering, volumetric clouds, and distance haze, matching the vibrant purple and orange hues of the target.",
|
||||||
|
"Design and integrate realistic character models with detailed clothing, hair, and skin textures, and implement a robust animation system for natural movement."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# Realism report: `docs/screens/realism/2026-11-01-adopt`
|
||||||
|
|
||||||
|
Vision judge: `google/gemini-2.5-flash` (scores 0-10, higher is more realistic compared with the concept target).
|
||||||
|
|
||||||
|
| set | shot | target | gap ↓ | edges | detail | contrast | overall | lighting | materials | texture | backdrop |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| after | boulevard_day | boulevard_skyline_day | 0.172 | 23.21 | 8.88 | 38.78 | 1.0 | 1.0 | 1.0 | 1.0 | 3.0 |
|
||||||
|
| after | boulevard_night | hollywood_entrance_rain_night | 0.723 | 16.02 | 7.13 | 39.4 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 |
|
||||||
|
| after | entrance_day | hollywood_entrance_morning | 0.442 | 25.88 | 9.86 | 40.18 | 1.0 | 1.0 | 1.0 | 1.0 | 0.0 |
|
||||||
|
| after | entrance_late | hollywood_entrance_rain_night | 1.105 | 18.4 | 8.41 | 51.61 | 1.0 | 1.0 | 1.33 | 1.33 | 0.0 |
|
||||||
|
| after | joke_shop_door | joke_shop_door_closeup | 0.818 | 14.84 | 7.04 | 47.63 | 2.0 | 2.0 | 3.0 | 3.0 | 4.0 |
|
||||||
|
| after | street_day | boulevard_skyline_day | 0.148 | 18.69 | 7.42 | 42.69 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 |
|
||||||
|
| after | street_late | boulevard_skyline_dusk | 0.224 | 12.95 | 6.27 | 31.94 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||||
|
| after | street_night | boulevard_skyline_dusk | 0.253 | 10.97 | 5.57 | 24.27 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
|
||||||
|
| after | studio_door | studio_stage_door_night | 0.395 | 16.62 | 7.65 | 48.52 | 1.0 | 1.0 | 1.0 | 1.0 | 0.0 |
|
||||||
|
| before | boulevard_day | boulevard_skyline_day | 0.036 | 20.75 | 8.2 | 49.63 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
|
||||||
|
| before | boulevard_night | hollywood_entrance_rain_night | 1.231 | 21.77 | 8.45 | 50.38 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
|
||||||
|
| before | entrance_day | hollywood_entrance_morning | 0.308 | 26.19 | 9.27 | 56.39 | 1.0 | 1.0 | 1.0 | 1.0 | 0.0 |
|
||||||
|
| before | street_day | boulevard_skyline_day | 0.307 | 12.44 | 5.32 | 41.37 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||||
|
| before | street_night | boulevard_skyline_dusk | 0.337 | 11.94 | 5.35 | 48.34 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
|
||||||
|
|
||||||
|
Mean scores per set (all captured shots):
|
||||||
|
|
||||||
|
| set | shots | gap ↓ | overall | lighting | materials | texture_detail | backdrop | characters |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| after | 9 | 0.476 | 1.0 | 1.0 | 1.15 | 1.15 | 1.33 | 0.11 |
|
||||||
|
| before | 5 | 0.444 | 0.8 | 0.8 | 0.8 | 0.8 | 0.6 | 0.6 |
|
||||||
|
|
||||||
|
Mean over the shots every set captured (like for like):
|
||||||
|
|
||||||
|
| set | shots | gap ↓ | overall | lighting | materials | texture_detail | backdrop | characters |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| after | 5 | 0.348 | 1.0 | 1.0 | 1.0 | 1.0 | 1.6 | 0.2 |
|
||||||
|
|
||||||
|
**after / boulevard_day** fixes:
|
||||||
|
- Implement physically based rendering (PBR) materials for all assets, focusing on realistic albedo, roughness, and metallic maps to achieve accurate surface responses to light.
|
||||||
|
- Overhaul lighting system to include global illumination, realistic skybox/HDRI for ambient light, and accurate sun/directional light with proper shadows and atmospheric scattering (haze/fog).
|
||||||
|
- Replace placeholder low-polygon models with high-detail 3D assets for trees, street furniture, and buildings, ensuring proper UV mapping and texture application.
|
||||||
|
- Improve texture resolution and detail across all surfaces, adding normal maps for surface imperfections and detail, and ensuring textures are seamlessly tiled or uniquely mapped where appropriate.
|
||||||
|
- Develop a realistic character pipeline, including high-fidelity models, animations, and PBR materials for skin, clothing, and hair, to populate the scene.
|
||||||
|
|
||||||
|
**after / boulevard_night** fixes:
|
||||||
|
- Implement a physically based rendering (PBR) material pipeline for all assets to achieve realistic surface responses to light, especially for wet surfaces and brick.
|
||||||
|
- Overhaul the lighting system to include global illumination, volumetric fog/rain, and realistic light sources with proper falloff and color temperature, focusing on dramatic contrast and mood.
|
||||||
|
- Replace low-polygon models with high-detail meshes and high-resolution textures, including normal and roughness maps, for all environmental elements (buildings, street, props, characters).
|
||||||
|
- Redesign the backdrop with a more detailed and atmospheric skyline, incorporating distance haze, subtle cloud formations, and appropriate night sky colors to match the target's mood.
|
||||||
|
- Update character models to be realistic, with detailed clothing, skin, and hair textures, and implement realistic animation and shading.
|
||||||
|
|
||||||
|
**after / entrance_day** fixes:
|
||||||
|
- Implement a physically based rendering (PBR) material pipeline for all assets to achieve realistic surface responses to light.
|
||||||
|
- Overhaul the lighting system to include global illumination, realistic skyboxes with atmospheric scattering, and dynamic shadows.
|
||||||
|
- Replace placeholder low-polygon models with high-fidelity assets that include detailed normal maps and displacement maps for architectural elements, foliage, and street furniture.
|
||||||
|
- Develop a detailed backdrop with distant buildings, varied skyline, and atmospheric haze to create depth and realism.
|
||||||
|
- Add realistic foliage models with proper leaf textures, branch structures, and varied forms instead of simple geometric shapes.
|
||||||
|
|
||||||
|
**after / entrance_late** fixes:
|
||||||
|
- Implement volumetric fog and rain effects to create atmospheric depth and mood, especially around light sources.
|
||||||
|
- Overhaul lighting system to include realistic light falloff, bounce light, and wet surface reflections, focusing on the marquee and streetlights.
|
||||||
|
- Redesign all materials with PBR workflows, ensuring accurate roughness, metallic, and normal maps for brick, concrete, metal, and wet surfaces.
|
||||||
|
- Add high-resolution, detailed textures for all surfaces, particularly the brickwork, sidewalk, and marquee, to capture fine imperfections and wear.
|
||||||
|
- Develop a detailed backdrop with distant buildings, subtle city lights, and atmospheric haze to replace the current flat sky.
|
||||||
|
|
||||||
|
**after / joke_shop_door** fixes:
|
||||||
|
- Implement physically based rendering (PBR) materials for all surfaces, especially brick, wood, and metal, to achieve realistic light interaction and surface properties.
|
||||||
|
- Overhaul the lighting system to include more nuanced light sources, volumetric effects, and global illumination. Focus on creating warm, inviting pools of light around entrances and signs, with realistic falloff and shadows.
|
||||||
|
- Improve texture resolution and detail for all assets, particularly the brickwork, ground, and signage. Add normal maps and displacement maps to give surfaces depth and tactile quality.
|
||||||
|
- Redesign the 'Comedy Club' sign to be a prominent, well-lit feature with appropriate emissive materials and a more stylized font, similar to the target image's 'COMEDY CLUB' sign.
|
||||||
|
- Enhance the backdrop with more detailed city lights, atmospheric haze, and a more realistic skybox to create a sense of depth and a believable urban environment.
|
||||||
|
|
||||||
|
**after / street_day** fixes:
|
||||||
|
- Implement physically based rendering (PBR) materials for all assets, focusing on realistic albedo, roughness, and metallic values to achieve accurate light interaction.
|
||||||
|
- Overhaul the lighting system to include a directional light representing the sun, a skybox/HDRI for ambient light, and global illumination to simulate bounced light, matching the target's clear, bright, and slightly warm daylight.
|
||||||
|
- Replace placeholder geometry with high-fidelity 3D models for cars, buildings, and environmental elements (e.g., palm trees, streetlights, sidewalks) that match the realistic proportions and details of the target image.
|
||||||
|
- Develop detailed textures for all surfaces, including road asphalt, concrete sidewalks, building facades, and foliage, ensuring appropriate resolution and variety to avoid tiling artifacts and enhance realism.
|
||||||
|
- Create a realistic backdrop with a detailed skyline, atmospheric perspective (distance haze), and a clear blue sky that accurately reflects the target's Los Angeles aesthetic, integrating it seamlessly with the foreground.
|
||||||
|
|
||||||
|
**after / street_late** fixes:
|
||||||
|
- Implement a full physically-based lighting model with global illumination, volumetric fog, and atmospheric scattering to achieve realistic sunset colors and light interaction.
|
||||||
|
- Replace placeholder geometry with detailed 3D models for buildings, palm trees, and vehicles, ensuring proper scale and architectural variety.
|
||||||
|
- Develop high-resolution PBR (Physically Based Rendering) materials for all surfaces, including reflective wet asphalt, glowing neon signs, and varied building facades, with appropriate roughness, metallic, and normal maps.
|
||||||
|
- Create a dynamic skybox system that accurately renders sunset gradients, distant city skylines with emissive windows, and atmospheric haze, replacing the current flat backdrop.
|
||||||
|
- Add realistic character models with appropriate clothing and animations to populate the scene, contributing to the overall realism and sense of a living city.
|
||||||
|
|
||||||
|
**after / street_night** fixes:
|
||||||
|
- Implement a full physically-based lighting model with global illumination, volumetric fog, and proper emissive materials for signs and car lights to capture the sunset glow and light trails.
|
||||||
|
- Replace placeholder geometry with detailed 3D models for buildings, palm trees, and vehicles, ensuring accurate proportions and architectural details.
|
||||||
|
- Develop high-resolution PBR textures for all surfaces (roads, buildings, signs, vehicles) with appropriate albedo, roughness, metallic, and normal maps to add realism and detail.
|
||||||
|
- Create a dynamic skybox with a realistic sunset gradient, distant city skyline with illuminated windows, and atmospheric haze to match the target's depth and mood.
|
||||||
|
- Add realistic character models with appropriate clothing and animations to populate the scene and enhance the sense of a living city.
|
||||||
|
|
||||||
|
**after / studio_door** fixes:
|
||||||
|
- Implement physically based rendering (PBR) materials for all surfaces, focusing on realistic albedo, roughness, and metallic values to achieve accurate light interaction.
|
||||||
|
- Overhaul the lighting system to include global illumination, volumetric fog, and realistic light sources with proper falloff and color temperature, especially for the marquee and wall sconces.
|
||||||
|
- Add high-resolution, detailed textures with normal and displacement maps for brickwork, concrete, and metallic elements to introduce surface imperfections and depth.
|
||||||
|
- Develop a realistic backdrop with a distant city skyline, atmospheric haze, and a dark, moody sky to establish a sense of depth and environment.
|
||||||
|
- Introduce realistic 3D character models with proper clothing, hair, and skin shaders, along with a high-fidelity car model, to populate the scene and match the target's visual complexity.
|
||||||
|
|
||||||
|
**before / boulevard_day** fixes:
|
||||||
|
- Implement a physically based rendering (PBR) material system for all assets to achieve realistic surface interactions with light.
|
||||||
|
- Integrate a global illumination solution (e.g., ray tracing, baked lightmaps) to simulate realistic light bouncing and ambient occlusion.
|
||||||
|
- Replace placeholder low-resolution textures with high-resolution, detailed textures, including normal maps and roughness maps for all surfaces.
|
||||||
|
- Develop a realistic skybox and distant cityscape with atmospheric scattering and haze effects to match the target's backdrop.
|
||||||
|
- Redesign character models with realistic proportions, detailed clothing, and proper skin shaders, along with animation systems for natural movement.
|
||||||
|
|
||||||
|
**before / boulevard_night** fixes:
|
||||||
|
- Implement a physically based rendering (PBR) material pipeline for all assets to achieve realistic surface responses to light.
|
||||||
|
- Overhaul the lighting system to include global illumination, volumetric effects (fog/haze), and realistic light sources with proper falloff and color temperature.
|
||||||
|
- Redesign character models with higher polygon counts, detailed textures (albedo, normal, roughness, metallic maps), and realistic rigging/animation.
|
||||||
|
- Replace placeholder textures with high-resolution, detailed textures that include normal maps for surface imperfections and depth.
|
||||||
|
- Develop a more complex backdrop with detailed buildings, atmospheric perspective (distance haze), and a dynamic sky system that reflects time of day and weather.
|
||||||
|
|
||||||
|
**before / entrance_day** fixes:
|
||||||
|
- Implement a physically based rendering (PBR) material pipeline for all assets to achieve realistic surface interactions with light.
|
||||||
|
- Overhaul the lighting system to include global illumination, realistic skyboxes with atmospheric scattering, and dynamic shadows to match the soft, warm lighting of the target.
|
||||||
|
- Replace placeholder textures with high-resolution, detailed textures that include normal maps, roughness maps, and metallic maps to add surface complexity and realism.
|
||||||
|
- Develop a detailed backdrop with distant buildings, realistic palm trees, and atmospheric haze to create depth and a sense of place.
|
||||||
|
- Redesign all environmental props (trees, trash cans, bollards) with realistic models and textures, moving away from the current stylized, low-poly aesthetic.
|
||||||
|
|
||||||
|
**before / street_day** fixes:
|
||||||
|
- Implement a physically based rendering (PBR) material system for all assets to achieve realistic surface interactions with light.
|
||||||
|
- Develop a dynamic sky and atmospheric scattering system to accurately simulate sky color, haze, and sun position based on time of day.
|
||||||
|
- Replace placeholder block models with detailed 3D models for buildings, vehicles, and environmental props, including proper UV mapping and high-resolution textures.
|
||||||
|
- Integrate a global illumination solution (e.g., ray tracing or baked lightmaps) to simulate realistic indirect lighting and soft shadows.
|
||||||
|
- Create realistic character models with detailed textures, proper rigging, and animations, ensuring they blend seamlessly with the environment.
|
||||||
|
|
||||||
|
**before / street_night** fixes:
|
||||||
|
- Implement a physically based lighting model with global illumination to accurately simulate light bounces and color bleeding, especially for the sunset and artificial lights.
|
||||||
|
- Replace placeholder geometry with detailed 3D models for buildings, vehicles, and environmental props, ensuring proper UV mapping and high-resolution textures.
|
||||||
|
- Develop a comprehensive material library with realistic PBR (Physically Based Rendering) materials for various surfaces (e.g., wet asphalt, neon signs, building facades, car paint) including roughness, metallic, and normal maps.
|
||||||
|
- Create a dynamic skybox system that accurately represents a sunset with atmospheric scattering, volumetric clouds, and distance haze, matching the vibrant purple and orange hues of the target.
|
||||||
|
- Design and integrate realistic character models with detailed clothing, hair, and skin textures, and implement a robust animation system for natural movement.
|
||||||
|
After Width: | Height: | Size: 593 KiB |
@@ -0,0 +1,526 @@
|
|||||||
|
{
|
||||||
|
"run": "docs/screens/realism/2026-11-01-v2",
|
||||||
|
"judge_model": "google/gemini-2.5-flash-lite",
|
||||||
|
"sets": {
|
||||||
|
"baseline": {
|
||||||
|
"boulevard_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 20.75,
|
||||||
|
"fine_detail": 8.2,
|
||||||
|
"contrast": 49.63,
|
||||||
|
"saturation_mean": 72.04,
|
||||||
|
"colour_spread": 55.45
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.036,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 4.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve the lighting model to include more realistic global illumination and soft shadows. The current lighting is too flat and harsh.",
|
||||||
|
"Replace the current low-polygon character models with more detailed and anatomically correct models. Add variations in clothing and appearance.",
|
||||||
|
"Increase the texture resolution and detail on all surfaces, especially buildings, roads, and foliage. Implement normal maps and specular maps for greater realism.",
|
||||||
|
"Enhance the backdrop by adding more detailed building models in the distance, atmospheric perspective (haze), and a more nuanced skybox.",
|
||||||
|
"Refine the material properties of all objects to better simulate real-world materials. This includes adjusting reflectivity, roughness, and subsurface scattering where appropriate."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boulevard_night": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 21.77,
|
||||||
|
"fine_detail": 8.45,
|
||||||
|
"contrast": 50.38,
|
||||||
|
"saturation_mean": 95.12,
|
||||||
|
"colour_spread": 55.61
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.43,
|
||||||
|
"fine_detail": 3.51,
|
||||||
|
"contrast": 27.34,
|
||||||
|
"saturation_mean": 159.44,
|
||||||
|
"colour_spread": 26.61
|
||||||
|
},
|
||||||
|
"metric_gap": 1.231,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 2.0,
|
||||||
|
"materials": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 2.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 1.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the dramatic, atmospheric, and volumetric lighting of the target image, focusing on warm, directional light from the marquee and streetlamp, and realistic reflections on wet surfaces.",
|
||||||
|
"Significantly upgrade material shaders to achieve realistic PBR (Physically Based Rendering) properties, especially for brick, asphalt, glass, and metal, to mimic the specular highlights, subsurface scattering, and roughness of the target.",
|
||||||
|
"Increase texture resolution and detail for all surfaces, particularly the brickwork, sidewalk, and building facades, to match the fine-grained realism of the target.",
|
||||||
|
"Redesign character models with higher polygon counts, more detailed textures, and realistic clothing and facial features to match the stylized but grounded look of the target's lone figure.",
|
||||||
|
"Enhance the backdrop by adding atmospheric effects like fog, rain, and distant city lights, and refine the building models to have more architectural detail and variation, similar to the target's moody urban environment."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrance_day": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_morning.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 26.19,
|
||||||
|
"fine_detail": 9.27,
|
||||||
|
"contrast": 56.39,
|
||||||
|
"saturation_mean": 65.24,
|
||||||
|
"colour_spread": 59.96
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 16.87,
|
||||||
|
"fine_detail": 6.92,
|
||||||
|
"contrast": 70.27,
|
||||||
|
"saturation_mean": 121.02,
|
||||||
|
"colour_spread": 69.83
|
||||||
|
},
|
||||||
|
"metric_gap": 0.308,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 10.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve brick texture and material to match the target's depth and variation.",
|
||||||
|
"Add more detailed and varied foliage, especially for the trees and bushes, to match the target's organic look.",
|
||||||
|
"Enhance lighting to create softer shadows, more atmospheric haze, and warmer tones, particularly around the building entrance and street.",
|
||||||
|
"Refine building details, such as window frames, signage, and architectural elements, to increase realism.",
|
||||||
|
"Introduce subtle imperfections and wear on surfaces like the sidewalk and building facade to mimic real-world aging."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 12.44,
|
||||||
|
"fine_detail": 5.32,
|
||||||
|
"contrast": 41.37,
|
||||||
|
"saturation_mean": 77.84,
|
||||||
|
"colour_spread": 44.96
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.307,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 2.0,
|
||||||
|
"materials": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 2.0,
|
||||||
|
"characters": 2.0,
|
||||||
|
"overall": 1.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the warm, directional sunlight and soft shadows of the target. Add volumetric fog for atmospheric perspective.",
|
||||||
|
"Replace flat, untextured building models with detailed geometry and realistic materials (concrete, glass, metal).",
|
||||||
|
"Add high-resolution textures to all surfaces, especially the road, sidewalk, and buildings, to mimic the detail in the target.",
|
||||||
|
"Model a more detailed and varied cityscape for the backdrop, including buildings with distinct architectural styles and realistic lighting.",
|
||||||
|
"Redesign character models to be more anatomically correct and have realistic clothing textures and animations."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_night": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 11.94,
|
||||||
|
"fine_detail": 5.35,
|
||||||
|
"contrast": 48.34,
|
||||||
|
"saturation_mean": 98.21,
|
||||||
|
"colour_spread": 48.85
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.83,
|
||||||
|
"fine_detail": 4.61,
|
||||||
|
"contrast": 33.26,
|
||||||
|
"saturation_mean": 188.16,
|
||||||
|
"colour_spread": 35.38
|
||||||
|
},
|
||||||
|
"metric_gap": 0.337,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 2.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the dramatic sunset and neon glow of the target image, focusing on volumetric lighting and bloom effects.",
|
||||||
|
"Enhance material properties for buildings, road, and car to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and specularity.",
|
||||||
|
"Increase texture resolution and detail on all surfaces, especially the road and building facades, to mimic the fine grain and imperfections seen in the target.",
|
||||||
|
"Redesign the backdrop to include a more detailed and atmospheric skyline with realistic haze and a richer sunset color gradient.",
|
||||||
|
"Replace the current character models with more detailed and realistically proportioned characters that fit the urban environment."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"candidate": {
|
||||||
|
"boulevard_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 20.67,
|
||||||
|
"fine_detail": 7.8,
|
||||||
|
"contrast": 42.49,
|
||||||
|
"saturation_mean": 67.55,
|
||||||
|
"colour_spread": 48.32
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.113,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 5.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve road and sidewalk materials to have more realistic specular highlights and surface imperfections.",
|
||||||
|
"Increase texture resolution and detail on buildings, especially the brickwork, to match the target's fidelity.",
|
||||||
|
"Replace stylized character models with realistic human models that have appropriate proportions and clothing.",
|
||||||
|
"Enhance lighting to create softer shadows, more realistic ambient occlusion, and better global illumination, especially around the streetlights and building facades.",
|
||||||
|
"Add atmospheric perspective and volumetric fog to the distant cityscape to create a more convincing sense of depth and distance haze."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boulevard_night": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 20.67,
|
||||||
|
"fine_detail": 7.86,
|
||||||
|
"contrast": 46.43,
|
||||||
|
"saturation_mean": 98.69,
|
||||||
|
"colour_spread": 50.59
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.43,
|
||||||
|
"fine_detail": 3.51,
|
||||||
|
"contrast": 27.34,
|
||||||
|
"saturation_mean": 159.44,
|
||||||
|
"colour_spread": 26.61
|
||||||
|
},
|
||||||
|
"metric_gap": 1.073,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 2.0,
|
||||||
|
"materials": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 2.0,
|
||||||
|
"overall": 1.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to create atmospheric mood, with strong contrast and volumetric effects like fog/rain.",
|
||||||
|
"Enhance material properties for realism: add specular highlights, subsurface scattering for skin, and more detailed surface imperfections.",
|
||||||
|
"Increase texture resolution and detail, especially on buildings, pavement, and clothing, to match the target's gritty realism.",
|
||||||
|
"Redesign characters to have more realistic proportions, clothing, and facial features, and animate them with more natural movement.",
|
||||||
|
"Develop a more detailed and atmospheric backdrop, including realistic city buildings, distant haze, and appropriate weather effects."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrance_day": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_morning.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 24.54,
|
||||||
|
"fine_detail": 8.92,
|
||||||
|
"contrast": 52.41,
|
||||||
|
"saturation_mean": 61.63,
|
||||||
|
"colour_spread": 54.98
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 16.87,
|
||||||
|
"fine_detail": 6.92,
|
||||||
|
"contrast": 70.27,
|
||||||
|
"saturation_mean": 121.02,
|
||||||
|
"colour_spread": 69.83
|
||||||
|
},
|
||||||
|
"metric_gap": 0.303,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve brick texture and material to add more variation, grime, and imperfections.",
|
||||||
|
"Enhance lighting to create softer shadows, more atmospheric haze, and realistic light falloff.",
|
||||||
|
"Add more detail and variation to the building facade, including window frames, signage, and architectural elements.",
|
||||||
|
"Replace low-poly trees and bushes with more detailed and realistic foliage models.",
|
||||||
|
"Refine character models to match the realistic style of the target image, focusing on proportions, clothing, and facial features."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 14.54,
|
||||||
|
"fine_detail": 5.32,
|
||||||
|
"contrast": 36.3,
|
||||||
|
"saturation_mean": 74.49,
|
||||||
|
"colour_spread": 39.9
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.326,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 2.0,
|
||||||
|
"materials": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 1.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve road and sidewalk materials to have more realistic specular highlights and surface imperfections.",
|
||||||
|
"Add significant detail to the car models, including realistic reflections, panel gaps, and tire textures.",
|
||||||
|
"Implement volumetric fog and atmospheric scattering to create a more convincing sense of depth and haze in the backdrop.",
|
||||||
|
"Replace the placeholder character with a detailed, realistically lit 3D model that matches the environment's style.",
|
||||||
|
"Enhance the lighting model to include softer shadows, more accurate ambient occlusion, and realistic light bounce."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_night": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 16.12,
|
||||||
|
"fine_detail": 6.06,
|
||||||
|
"contrast": 48.36,
|
||||||
|
"saturation_mean": 103.44,
|
||||||
|
"colour_spread": 49.1
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.83,
|
||||||
|
"fine_detail": 4.61,
|
||||||
|
"contrast": 33.26,
|
||||||
|
"saturation_mean": 188.16,
|
||||||
|
"colour_spread": 35.38
|
||||||
|
},
|
||||||
|
"metric_gap": 0.495,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 4.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the dramatic, atmospheric sunset and neon glow of the target image. Add volumetric fog and bloom effects.",
|
||||||
|
"Significantly enhance material properties for buildings, road, and vehicles to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and surface imperfections.",
|
||||||
|
"Increase texture resolution and detail on all surfaces, especially buildings and the road, to match the crispness and complexity of the target.",
|
||||||
|
"Redesign the backdrop to include a more detailed and atmospheric city skyline with appropriate haze and color grading to match the sunset.",
|
||||||
|
"Completely overhaul character models to be significantly more detailed, with realistic proportions, clothing, and animation, to match the quality of the target scene."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"candidate_dusk": {
|
||||||
|
"boulevard_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 20.64,
|
||||||
|
"fine_detail": 7.79,
|
||||||
|
"contrast": 42.52,
|
||||||
|
"saturation_mean": 67.55,
|
||||||
|
"colour_spread": 48.35
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.113,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 5.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the warm, directional sunlight and soft shadows of the target. Add volumetric fog or atmospheric perspective to the distant cityscape.",
|
||||||
|
"Replace low-poly, stylized assets (trees, characters, buildings) with high-fidelity models that have realistic proportions and details.",
|
||||||
|
"Apply PBR materials with appropriate roughness, metallic, and normal maps to all surfaces (road, sidewalk, buildings, foliage) to simulate real-world material properties.",
|
||||||
|
"Increase texture resolution and add surface imperfections (grime, wear, subtle variations) to the road, sidewalk, and building facades.",
|
||||||
|
"Refine character models to have realistic anatomy, clothing, and animation, and ensure they are properly lit and integrated into the scene."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boulevard_night": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 21.29,
|
||||||
|
"fine_detail": 7.93,
|
||||||
|
"contrast": 43.3,
|
||||||
|
"saturation_mean": 97.44,
|
||||||
|
"colour_spread": 47.83
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.43,
|
||||||
|
"fine_detail": 3.51,
|
||||||
|
"contrast": 27.34,
|
||||||
|
"saturation_mean": 159.44,
|
||||||
|
"colour_spread": 26.61
|
||||||
|
},
|
||||||
|
"metric_gap": 1.041,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 4.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve the lighting to match the dramatic, atmospheric, and volumetric lighting of the target image, focusing on warm, directional light from the marquee and streetlamp, and realistic reflections on wet surfaces.",
|
||||||
|
"Significantly upgrade the character models and animations to be more realistic, with detailed clothing, facial features, and natural posing, rather than the current low-poly, cartoonish style.",
|
||||||
|
"Enhance material properties and textures for all surfaces (brick, pavement, doors, clothing) to achieve a photorealistic look, with appropriate roughness, reflectivity, and detailed normal maps, especially for the wet environment.",
|
||||||
|
"Refine the backdrop elements, including the distant city skyline and atmospheric haze, to create a sense of depth and realism consistent with the target image's moody atmosphere.",
|
||||||
|
"Implement more realistic environmental effects, such as rain, puddles with accurate reflections, and subtle fog, to enhance the overall immersion and match the target's gritty, wet ambiance."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrance_day": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_morning.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 24.44,
|
||||||
|
"fine_detail": 8.87,
|
||||||
|
"contrast": 52.25,
|
||||||
|
"saturation_mean": 61.51,
|
||||||
|
"colour_spread": 54.84
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 16.87,
|
||||||
|
"fine_detail": 6.92,
|
||||||
|
"contrast": 70.27,
|
||||||
|
"saturation_mean": 121.02,
|
||||||
|
"colour_spread": 69.83
|
||||||
|
},
|
||||||
|
"metric_gap": 0.3,
|
||||||
|
"judge": {
|
||||||
|
"scores": {},
|
||||||
|
"fixes": [
|
||||||
|
"judge failed: OpenRouter HTTP 400: {\"error\":{\"message\":\"Provider returned error\",\"code\":400,\"metadata\":{\"raw\":\"{\\n \\\"error\\\": {\\n \\\"code\\\": 400,\\n \\\"message\\\": \\\"API key not valid. Please pass a valid API key.\\\",\\n \\\"status\\\": \\\"INVALID_ARGUMENT\\\",\\n \\\"details\\\": [\\n {\\n \\\"@type\\\": \\\"type.googleapis.com/googl"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 14.55,
|
||||||
|
"fine_detail": 5.33,
|
||||||
|
"contrast": 36.3,
|
||||||
|
"saturation_mean": 74.49,
|
||||||
|
"colour_spread": 39.9
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.326,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 4.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve road and sidewalk material PBR values to match the target's roughness and reflectivity.",
|
||||||
|
"Increase the resolution and detail of building textures in the backdrop to reduce the blurry, low-poly appearance.",
|
||||||
|
"Add atmospheric perspective and volumetric fog to the distant cityscape to mimic the target's depth and haze.",
|
||||||
|
"Enhance car material properties, particularly reflections and specular highlights, to match the target's realism.",
|
||||||
|
"Redesign character models to have more detailed geometry, textures, and more natural proportions and posing."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_night": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 17.04,
|
||||||
|
"fine_detail": 6.13,
|
||||||
|
"contrast": 43.69,
|
||||||
|
"saturation_mean": 96.82,
|
||||||
|
"colour_spread": 45.18
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.83,
|
||||||
|
"fine_detail": 4.61,
|
||||||
|
"contrast": 33.26,
|
||||||
|
"saturation_mean": 188.16,
|
||||||
|
"colour_spread": 35.38
|
||||||
|
},
|
||||||
|
"metric_gap": 0.463,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the dramatic, atmospheric sunset and neon glow of the target image. Add volumetric fog and bloom effects.",
|
||||||
|
"Enhance material properties for buildings, road, and vehicles to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and specularity.",
|
||||||
|
"Increase texture resolution and detail on all surfaces, especially buildings and the road, to match the fine details seen in the target.",
|
||||||
|
"Redesign the backdrop to include a more detailed and atmospheric city skyline with appropriate haze and color grading to mimic the target's sunset.",
|
||||||
|
"Significantly rework character models to be more realistic in terms of proportions, texturing, and animation, as the current models are very stylized and cartoony."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# Realism report: `docs/screens/realism/2026-11-01-v2`
|
||||||
|
|
||||||
|
Vision judge: `google/gemini-2.5-flash-lite` (scores 0-10, higher is more realistic compared with the concept target).
|
||||||
|
|
||||||
|
| set | shot | target | gap ↓ | edges | detail | contrast | overall | lighting | materials | texture | backdrop |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| baseline | boulevard_day | boulevard_skyline_day | 0.036 | 20.75 | 8.2 | 49.63 | 2.0 | 3.0 | 2.0 | 1.0 | 4.0 |
|
||||||
|
| baseline | boulevard_night | hollywood_entrance_rain_night | 1.231 | 21.77 | 8.45 | 50.38 | 1.0 | 2.0 | 1.0 | 1.0 | 2.0 |
|
||||||
|
| baseline | entrance_day | hollywood_entrance_morning | 0.308 | 26.19 | 9.27 | 56.39 | 2.0 | 3.0 | 2.0 | 2.0 | 3.0 |
|
||||||
|
| baseline | street_day | boulevard_skyline_day | 0.307 | 12.44 | 5.32 | 41.37 | 1.0 | 2.0 | 1.0 | 1.0 | 2.0 |
|
||||||
|
| baseline | street_night | boulevard_skyline_dusk | 0.337 | 11.94 | 5.35 | 48.34 | 2.0 | 3.0 | 2.0 | 1.0 | 2.0 |
|
||||||
|
| candidate | boulevard_day | boulevard_skyline_day | 0.113 | 20.67 | 7.8 | 42.49 | 2.0 | 3.0 | 2.0 | 2.0 | 5.0 |
|
||||||
|
| candidate | boulevard_night | hollywood_entrance_rain_night | 1.073 | 20.67 | 7.86 | 46.43 | 1.0 | 2.0 | 1.0 | 1.0 | 3.0 |
|
||||||
|
| candidate | entrance_day | hollywood_entrance_morning | 0.303 | 24.54 | 8.92 | 52.41 | 2.0 | 3.0 | 2.0 | 2.0 | 3.0 |
|
||||||
|
| candidate | street_day | boulevard_skyline_day | 0.326 | 14.54 | 5.32 | 36.3 | 1.0 | 2.0 | 1.0 | 1.0 | 3.0 |
|
||||||
|
| candidate | street_night | boulevard_skyline_dusk | 0.495 | 16.12 | 6.06 | 48.36 | 2.0 | 3.0 | 2.0 | 2.0 | 4.0 |
|
||||||
|
| candidate_dusk | boulevard_day | boulevard_skyline_day | 0.113 | 20.64 | 7.79 | 42.52 | 2.0 | 3.0 | 2.0 | 2.0 | 5.0 |
|
||||||
|
| candidate_dusk | boulevard_night | hollywood_entrance_rain_night | 1.041 | 21.29 | 7.93 | 43.3 | 2.0 | 3.0 | 2.0 | 2.0 | 4.0 |
|
||||||
|
| candidate_dusk | entrance_day | hollywood_entrance_morning | 0.3 | 24.44 | 8.87 | 52.25 | - | - | - | - | - |
|
||||||
|
| candidate_dusk | street_day | boulevard_skyline_day | 0.326 | 14.55 | 5.33 | 36.3 | 2.0 | 3.0 | 2.0 | 1.0 | 4.0 |
|
||||||
|
| candidate_dusk | street_night | boulevard_skyline_dusk | 0.463 | 17.04 | 6.13 | 43.69 | 2.0 | 3.0 | 2.0 | 2.0 | 3.0 |
|
||||||
|
|
||||||
|
Mean scores per set:
|
||||||
|
|
||||||
|
| set | gap ↓ | overall | lighting | materials | texture_detail | backdrop | characters |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| baseline | 0.444 | 1.6 | 2.6 | 1.6 | 1.2 | 2.6 | 3.0 |
|
||||||
|
| candidate | 0.462 | 1.6 | 2.6 | 1.6 | 1.6 | 3.6 | 1.2 |
|
||||||
|
| candidate_dusk | 0.449 | 2.0 | 3.0 | 2.0 | 1.75 | 4.0 | 1.0 |
|
||||||
|
|
||||||
|
**baseline / boulevard_day** fixes:
|
||||||
|
- Improve the lighting model to include more realistic global illumination and soft shadows. The current lighting is too flat and harsh.
|
||||||
|
- Replace the current low-polygon character models with more detailed and anatomically correct models. Add variations in clothing and appearance.
|
||||||
|
- Increase the texture resolution and detail on all surfaces, especially buildings, roads, and foliage. Implement normal maps and specular maps for greater realism.
|
||||||
|
- Enhance the backdrop by adding more detailed building models in the distance, atmospheric perspective (haze), and a more nuanced skybox.
|
||||||
|
- Refine the material properties of all objects to better simulate real-world materials. This includes adjusting reflectivity, roughness, and subsurface scattering where appropriate.
|
||||||
|
|
||||||
|
**baseline / boulevard_night** fixes:
|
||||||
|
- Improve lighting to match the dramatic, atmospheric, and volumetric lighting of the target image, focusing on warm, directional light from the marquee and streetlamp, and realistic reflections on wet surfaces.
|
||||||
|
- Significantly upgrade material shaders to achieve realistic PBR (Physically Based Rendering) properties, especially for brick, asphalt, glass, and metal, to mimic the specular highlights, subsurface scattering, and roughness of the target.
|
||||||
|
- Increase texture resolution and detail for all surfaces, particularly the brickwork, sidewalk, and building facades, to match the fine-grained realism of the target.
|
||||||
|
- Redesign character models with higher polygon counts, more detailed textures, and realistic clothing and facial features to match the stylized but grounded look of the target's lone figure.
|
||||||
|
- Enhance the backdrop by adding atmospheric effects like fog, rain, and distant city lights, and refine the building models to have more architectural detail and variation, similar to the target's moody urban environment.
|
||||||
|
|
||||||
|
**baseline / entrance_day** fixes:
|
||||||
|
- Improve brick texture and material to match the target's depth and variation.
|
||||||
|
- Add more detailed and varied foliage, especially for the trees and bushes, to match the target's organic look.
|
||||||
|
- Enhance lighting to create softer shadows, more atmospheric haze, and warmer tones, particularly around the building entrance and street.
|
||||||
|
- Refine building details, such as window frames, signage, and architectural elements, to increase realism.
|
||||||
|
- Introduce subtle imperfections and wear on surfaces like the sidewalk and building facade to mimic real-world aging.
|
||||||
|
|
||||||
|
**baseline / street_day** fixes:
|
||||||
|
- Improve lighting to match the warm, directional sunlight and soft shadows of the target. Add volumetric fog for atmospheric perspective.
|
||||||
|
- Replace flat, untextured building models with detailed geometry and realistic materials (concrete, glass, metal).
|
||||||
|
- Add high-resolution textures to all surfaces, especially the road, sidewalk, and buildings, to mimic the detail in the target.
|
||||||
|
- Model a more detailed and varied cityscape for the backdrop, including buildings with distinct architectural styles and realistic lighting.
|
||||||
|
- Redesign character models to be more anatomically correct and have realistic clothing textures and animations.
|
||||||
|
|
||||||
|
**baseline / street_night** fixes:
|
||||||
|
- Improve lighting to match the dramatic sunset and neon glow of the target image, focusing on volumetric lighting and bloom effects.
|
||||||
|
- Enhance material properties for buildings, road, and car to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and specularity.
|
||||||
|
- Increase texture resolution and detail on all surfaces, especially the road and building facades, to mimic the fine grain and imperfections seen in the target.
|
||||||
|
- Redesign the backdrop to include a more detailed and atmospheric skyline with realistic haze and a richer sunset color gradient.
|
||||||
|
- Replace the current character models with more detailed and realistically proportioned characters that fit the urban environment.
|
||||||
|
|
||||||
|
**candidate / boulevard_day** fixes:
|
||||||
|
- Improve road and sidewalk materials to have more realistic specular highlights and surface imperfections.
|
||||||
|
- Increase texture resolution and detail on buildings, especially the brickwork, to match the target's fidelity.
|
||||||
|
- Replace stylized character models with realistic human models that have appropriate proportions and clothing.
|
||||||
|
- Enhance lighting to create softer shadows, more realistic ambient occlusion, and better global illumination, especially around the streetlights and building facades.
|
||||||
|
- Add atmospheric perspective and volumetric fog to the distant cityscape to create a more convincing sense of depth and distance haze.
|
||||||
|
|
||||||
|
**candidate / boulevard_night** fixes:
|
||||||
|
- Improve lighting to create atmospheric mood, with strong contrast and volumetric effects like fog/rain.
|
||||||
|
- Enhance material properties for realism: add specular highlights, subsurface scattering for skin, and more detailed surface imperfections.
|
||||||
|
- Increase texture resolution and detail, especially on buildings, pavement, and clothing, to match the target's gritty realism.
|
||||||
|
- Redesign characters to have more realistic proportions, clothing, and facial features, and animate them with more natural movement.
|
||||||
|
- Develop a more detailed and atmospheric backdrop, including realistic city buildings, distant haze, and appropriate weather effects.
|
||||||
|
|
||||||
|
**candidate / entrance_day** fixes:
|
||||||
|
- Improve brick texture and material to add more variation, grime, and imperfections.
|
||||||
|
- Enhance lighting to create softer shadows, more atmospheric haze, and realistic light falloff.
|
||||||
|
- Add more detail and variation to the building facade, including window frames, signage, and architectural elements.
|
||||||
|
- Replace low-poly trees and bushes with more detailed and realistic foliage models.
|
||||||
|
- Refine character models to match the realistic style of the target image, focusing on proportions, clothing, and facial features.
|
||||||
|
|
||||||
|
**candidate / street_day** fixes:
|
||||||
|
- Improve road and sidewalk materials to have more realistic specular highlights and surface imperfections.
|
||||||
|
- Add significant detail to the car models, including realistic reflections, panel gaps, and tire textures.
|
||||||
|
- Implement volumetric fog and atmospheric scattering to create a more convincing sense of depth and haze in the backdrop.
|
||||||
|
- Replace the placeholder character with a detailed, realistically lit 3D model that matches the environment's style.
|
||||||
|
- Enhance the lighting model to include softer shadows, more accurate ambient occlusion, and realistic light bounce.
|
||||||
|
|
||||||
|
**candidate / street_night** fixes:
|
||||||
|
- Improve lighting to match the dramatic, atmospheric sunset and neon glow of the target image. Add volumetric fog and bloom effects.
|
||||||
|
- Significantly enhance material properties for buildings, road, and vehicles to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and surface imperfections.
|
||||||
|
- Increase texture resolution and detail on all surfaces, especially buildings and the road, to match the crispness and complexity of the target.
|
||||||
|
- Redesign the backdrop to include a more detailed and atmospheric city skyline with appropriate haze and color grading to match the sunset.
|
||||||
|
- Completely overhaul character models to be significantly more detailed, with realistic proportions, clothing, and animation, to match the quality of the target scene.
|
||||||
|
|
||||||
|
**candidate_dusk / boulevard_day** fixes:
|
||||||
|
- Improve lighting to match the warm, directional sunlight and soft shadows of the target. Add volumetric fog or atmospheric perspective to the distant cityscape.
|
||||||
|
- Replace low-poly, stylized assets (trees, characters, buildings) with high-fidelity models that have realistic proportions and details.
|
||||||
|
- Apply PBR materials with appropriate roughness, metallic, and normal maps to all surfaces (road, sidewalk, buildings, foliage) to simulate real-world material properties.
|
||||||
|
- Increase texture resolution and add surface imperfections (grime, wear, subtle variations) to the road, sidewalk, and building facades.
|
||||||
|
- Refine character models to have realistic anatomy, clothing, and animation, and ensure they are properly lit and integrated into the scene.
|
||||||
|
|
||||||
|
**candidate_dusk / boulevard_night** fixes:
|
||||||
|
- Improve the lighting to match the dramatic, atmospheric, and volumetric lighting of the target image, focusing on warm, directional light from the marquee and streetlamp, and realistic reflections on wet surfaces.
|
||||||
|
- Significantly upgrade the character models and animations to be more realistic, with detailed clothing, facial features, and natural posing, rather than the current low-poly, cartoonish style.
|
||||||
|
- Enhance material properties and textures for all surfaces (brick, pavement, doors, clothing) to achieve a photorealistic look, with appropriate roughness, reflectivity, and detailed normal maps, especially for the wet environment.
|
||||||
|
- Refine the backdrop elements, including the distant city skyline and atmospheric haze, to create a sense of depth and realism consistent with the target image's moody atmosphere.
|
||||||
|
- Implement more realistic environmental effects, such as rain, puddles with accurate reflections, and subtle fog, to enhance the overall immersion and match the target's gritty, wet ambiance.
|
||||||
|
|
||||||
|
**candidate_dusk / entrance_day** fixes:
|
||||||
|
- judge failed: OpenRouter HTTP 400: {"error":{"message":"Provider returned error","code":400,"metadata":{"raw":"{\n \"error\": {\n \"code\": 400,\n \"message\": \"API key not valid. Please pass a valid API key.\",\n \"status\": \"INVALID_ARGUMENT\",\n \"details\": [\n {\n \"@type\": \"type.googleapis.com/googl
|
||||||
|
|
||||||
|
**candidate_dusk / street_day** fixes:
|
||||||
|
- Improve road and sidewalk material PBR values to match the target's roughness and reflectivity.
|
||||||
|
- Increase the resolution and detail of building textures in the backdrop to reduce the blurry, low-poly appearance.
|
||||||
|
- Add atmospheric perspective and volumetric fog to the distant cityscape to mimic the target's depth and haze.
|
||||||
|
- Enhance car material properties, particularly reflections and specular highlights, to match the target's realism.
|
||||||
|
- Redesign character models to have more detailed geometry, textures, and more natural proportions and posing.
|
||||||
|
|
||||||
|
**candidate_dusk / street_night** fixes:
|
||||||
|
- Improve lighting to match the dramatic, atmospheric sunset and neon glow of the target image. Add volumetric fog and bloom effects.
|
||||||
|
- Enhance material properties for buildings, road, and vehicles to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and specularity.
|
||||||
|
- Increase texture resolution and detail on all surfaces, especially buildings and the road, to match the fine details seen in the target.
|
||||||
|
- Redesign the backdrop to include a more detailed and atmospheric city skyline with appropriate haze and color grading to mimic the target's sunset.
|
||||||
|
- Significantly rework character models to be more realistic in terms of proportions, texturing, and animation, as the current models are very stylized and cartoony.
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
{
|
||||||
|
"run": "docs/screens/realism/2026-11-01-v2",
|
||||||
|
"judge_model": "google/gemini-2.5-flash-lite",
|
||||||
|
"sets": {
|
||||||
|
"baseline": {
|
||||||
|
"boulevard_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 20.75,
|
||||||
|
"fine_detail": 8.2,
|
||||||
|
"contrast": 49.63,
|
||||||
|
"saturation_mean": 72.04,
|
||||||
|
"colour_spread": 55.45
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.036,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 4.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve the lighting model to include more realistic global illumination and soft shadows. The current lighting is too flat and harsh.",
|
||||||
|
"Replace the current low-polygon character models with more detailed and anatomically correct models. Add variations in clothing and appearance.",
|
||||||
|
"Increase the texture resolution and detail on all surfaces, especially buildings, roads, and foliage. Implement normal maps and specular maps for greater realism.",
|
||||||
|
"Enhance the backdrop by adding more detailed building models in the distance, atmospheric perspective (haze), and a more nuanced skybox.",
|
||||||
|
"Refine the material properties of all objects to better simulate real-world materials. This includes adjusting reflectivity, roughness, and subsurface scattering where appropriate."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boulevard_night": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 21.77,
|
||||||
|
"fine_detail": 8.45,
|
||||||
|
"contrast": 50.38,
|
||||||
|
"saturation_mean": 95.12,
|
||||||
|
"colour_spread": 55.61
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.43,
|
||||||
|
"fine_detail": 3.51,
|
||||||
|
"contrast": 27.34,
|
||||||
|
"saturation_mean": 159.44,
|
||||||
|
"colour_spread": 26.61
|
||||||
|
},
|
||||||
|
"metric_gap": 1.231,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 2.0,
|
||||||
|
"materials": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 2.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 1.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the dramatic, atmospheric, and volumetric lighting of the target image, focusing on warm, directional light from the marquee and streetlamp, and realistic reflections on wet surfaces.",
|
||||||
|
"Significantly upgrade material shaders to achieve realistic PBR (Physically Based Rendering) properties, especially for brick, asphalt, glass, and metal, to mimic the specular highlights, subsurface scattering, and roughness of the target.",
|
||||||
|
"Increase texture resolution and detail for all surfaces, particularly the brickwork, sidewalk, and building facades, to match the fine-grained realism of the target.",
|
||||||
|
"Redesign character models with higher polygon counts, more detailed textures, and realistic clothing and facial features to match the stylized but grounded look of the target's lone figure.",
|
||||||
|
"Enhance the backdrop by adding atmospheric effects like fog, rain, and distant city lights, and refine the building models to have more architectural detail and variation, similar to the target's moody urban environment."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrance_day": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_morning.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 26.19,
|
||||||
|
"fine_detail": 9.27,
|
||||||
|
"contrast": 56.39,
|
||||||
|
"saturation_mean": 65.24,
|
||||||
|
"colour_spread": 59.96
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 16.87,
|
||||||
|
"fine_detail": 6.92,
|
||||||
|
"contrast": 70.27,
|
||||||
|
"saturation_mean": 121.02,
|
||||||
|
"colour_spread": 69.83
|
||||||
|
},
|
||||||
|
"metric_gap": 0.308,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 10.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve brick texture and material to match the target's depth and variation.",
|
||||||
|
"Add more detailed and varied foliage, especially for the trees and bushes, to match the target's organic look.",
|
||||||
|
"Enhance lighting to create softer shadows, more atmospheric haze, and warmer tones, particularly around the building entrance and street.",
|
||||||
|
"Refine building details, such as window frames, signage, and architectural elements, to increase realism.",
|
||||||
|
"Introduce subtle imperfections and wear on surfaces like the sidewalk and building facade to mimic real-world aging."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 12.44,
|
||||||
|
"fine_detail": 5.32,
|
||||||
|
"contrast": 41.37,
|
||||||
|
"saturation_mean": 77.84,
|
||||||
|
"colour_spread": 44.96
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.307,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 2.0,
|
||||||
|
"materials": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 2.0,
|
||||||
|
"characters": 2.0,
|
||||||
|
"overall": 1.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the warm, directional sunlight and soft shadows of the target. Add volumetric fog for atmospheric perspective.",
|
||||||
|
"Replace flat, untextured building models with detailed geometry and realistic materials (concrete, glass, metal).",
|
||||||
|
"Add high-resolution textures to all surfaces, especially the road, sidewalk, and buildings, to mimic the detail in the target.",
|
||||||
|
"Model a more detailed and varied cityscape for the backdrop, including buildings with distinct architectural styles and realistic lighting.",
|
||||||
|
"Redesign character models to be more anatomically correct and have realistic clothing textures and animations."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_night": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 11.94,
|
||||||
|
"fine_detail": 5.35,
|
||||||
|
"contrast": 48.34,
|
||||||
|
"saturation_mean": 98.21,
|
||||||
|
"colour_spread": 48.85
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.83,
|
||||||
|
"fine_detail": 4.61,
|
||||||
|
"contrast": 33.26,
|
||||||
|
"saturation_mean": 188.16,
|
||||||
|
"colour_spread": 35.38
|
||||||
|
},
|
||||||
|
"metric_gap": 0.337,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 2.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the dramatic sunset and neon glow of the target image, focusing on volumetric lighting and bloom effects.",
|
||||||
|
"Enhance material properties for buildings, road, and car to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and specularity.",
|
||||||
|
"Increase texture resolution and detail on all surfaces, especially the road and building facades, to mimic the fine grain and imperfections seen in the target.",
|
||||||
|
"Redesign the backdrop to include a more detailed and atmospheric skyline with realistic haze and a richer sunset color gradient.",
|
||||||
|
"Replace the current character models with more detailed and realistically proportioned characters that fit the urban environment."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"candidate": {
|
||||||
|
"boulevard_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 20.67,
|
||||||
|
"fine_detail": 7.8,
|
||||||
|
"contrast": 42.49,
|
||||||
|
"saturation_mean": 67.55,
|
||||||
|
"colour_spread": 48.32
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.113,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 5.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve road and sidewalk materials to have more realistic specular highlights and surface imperfections.",
|
||||||
|
"Increase texture resolution and detail on buildings, especially the brickwork, to match the target's fidelity.",
|
||||||
|
"Replace stylized character models with realistic human models that have appropriate proportions and clothing.",
|
||||||
|
"Enhance lighting to create softer shadows, more realistic ambient occlusion, and better global illumination, especially around the streetlights and building facades.",
|
||||||
|
"Add atmospheric perspective and volumetric fog to the distant cityscape to create a more convincing sense of depth and distance haze."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boulevard_night": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_rain_night.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 20.67,
|
||||||
|
"fine_detail": 7.86,
|
||||||
|
"contrast": 46.43,
|
||||||
|
"saturation_mean": 98.69,
|
||||||
|
"colour_spread": 50.59
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.43,
|
||||||
|
"fine_detail": 3.51,
|
||||||
|
"contrast": 27.34,
|
||||||
|
"saturation_mean": 159.44,
|
||||||
|
"colour_spread": 26.61
|
||||||
|
},
|
||||||
|
"metric_gap": 1.073,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 2.0,
|
||||||
|
"materials": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 2.0,
|
||||||
|
"overall": 1.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to create atmospheric mood, with strong contrast and volumetric effects like fog/rain.",
|
||||||
|
"Enhance material properties for realism: add specular highlights, subsurface scattering for skin, and more detailed surface imperfections.",
|
||||||
|
"Increase texture resolution and detail, especially on buildings, pavement, and clothing, to match the target's gritty realism.",
|
||||||
|
"Redesign characters to have more realistic proportions, clothing, and facial features, and animate them with more natural movement.",
|
||||||
|
"Develop a more detailed and atmospheric backdrop, including realistic city buildings, distant haze, and appropriate weather effects."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entrance_day": {
|
||||||
|
"target": "docs/screens/concepts/hollywood_entrance_morning.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 24.54,
|
||||||
|
"fine_detail": 8.92,
|
||||||
|
"contrast": 52.41,
|
||||||
|
"saturation_mean": 61.63,
|
||||||
|
"colour_spread": 54.98
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 16.87,
|
||||||
|
"fine_detail": 6.92,
|
||||||
|
"contrast": 70.27,
|
||||||
|
"saturation_mean": 121.02,
|
||||||
|
"colour_spread": 69.83
|
||||||
|
},
|
||||||
|
"metric_gap": 0.303,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve brick texture and material to add more variation, grime, and imperfections.",
|
||||||
|
"Enhance lighting to create softer shadows, more atmospheric haze, and realistic light falloff.",
|
||||||
|
"Add more detail and variation to the building facade, including window frames, signage, and architectural elements.",
|
||||||
|
"Replace low-poly trees and bushes with more detailed and realistic foliage models.",
|
||||||
|
"Refine character models to match the realistic style of the target image, focusing on proportions, clothing, and facial features."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_day": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_day.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 14.54,
|
||||||
|
"fine_detail": 5.32,
|
||||||
|
"contrast": 36.3,
|
||||||
|
"saturation_mean": 74.49,
|
||||||
|
"colour_spread": 39.9
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 20.74,
|
||||||
|
"fine_detail": 8.23,
|
||||||
|
"contrast": 54.85,
|
||||||
|
"saturation_mean": 73.09,
|
||||||
|
"colour_spread": 58.16
|
||||||
|
},
|
||||||
|
"metric_gap": 0.326,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 2.0,
|
||||||
|
"materials": 1.0,
|
||||||
|
"texture_detail": 1.0,
|
||||||
|
"backdrop": 3.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 1.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve road and sidewalk materials to have more realistic specular highlights and surface imperfections.",
|
||||||
|
"Add significant detail to the car models, including realistic reflections, panel gaps, and tire textures.",
|
||||||
|
"Implement volumetric fog and atmospheric scattering to create a more convincing sense of depth and haze in the backdrop.",
|
||||||
|
"Replace the placeholder character with a detailed, realistically lit 3D model that matches the environment's style.",
|
||||||
|
"Enhance the lighting model to include softer shadows, more accurate ambient occlusion, and realistic light bounce."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"street_night": {
|
||||||
|
"target": "docs/screens/concepts/boulevard_skyline_dusk.png",
|
||||||
|
"metrics": {
|
||||||
|
"edge_density": 16.12,
|
||||||
|
"fine_detail": 6.06,
|
||||||
|
"contrast": 48.36,
|
||||||
|
"saturation_mean": 103.44,
|
||||||
|
"colour_spread": 49.1
|
||||||
|
},
|
||||||
|
"target_metrics": {
|
||||||
|
"edge_density": 8.83,
|
||||||
|
"fine_detail": 4.61,
|
||||||
|
"contrast": 33.26,
|
||||||
|
"saturation_mean": 188.16,
|
||||||
|
"colour_spread": 35.38
|
||||||
|
},
|
||||||
|
"metric_gap": 0.495,
|
||||||
|
"judge": {
|
||||||
|
"scores": {
|
||||||
|
"lighting": 3.0,
|
||||||
|
"materials": 2.0,
|
||||||
|
"texture_detail": 2.0,
|
||||||
|
"backdrop": 4.0,
|
||||||
|
"characters": 1.0,
|
||||||
|
"overall": 2.0
|
||||||
|
},
|
||||||
|
"fixes": [
|
||||||
|
"Improve lighting to match the dramatic, atmospheric sunset and neon glow of the target image. Add volumetric fog and bloom effects.",
|
||||||
|
"Significantly enhance material properties for buildings, road, and vehicles to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and surface imperfections.",
|
||||||
|
"Increase texture resolution and detail on all surfaces, especially buildings and the road, to match the crispness and complexity of the target.",
|
||||||
|
"Redesign the backdrop to include a more detailed and atmospheric city skyline with appropriate haze and color grading to match the sunset.",
|
||||||
|
"Completely overhaul character models to be significantly more detailed, with realistic proportions, clothing, and animation, to match the quality of the target scene."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Realism report: `docs/screens/realism/2026-11-01-v2`
|
||||||
|
|
||||||
|
Vision judge: `google/gemini-2.5-flash-lite` (scores 0-10, higher is more realistic compared with the concept target).
|
||||||
|
|
||||||
|
| set | shot | target | gap ↓ | edges | detail | contrast | overall | lighting | materials | texture | backdrop |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| baseline | boulevard_day | boulevard_skyline_day | 0.036 | 20.75 | 8.2 | 49.63 | 2.0 | 3.0 | 2.0 | 1.0 | 4.0 |
|
||||||
|
| baseline | boulevard_night | hollywood_entrance_rain_night | 1.231 | 21.77 | 8.45 | 50.38 | 1.0 | 2.0 | 1.0 | 1.0 | 2.0 |
|
||||||
|
| baseline | entrance_day | hollywood_entrance_morning | 0.308 | 26.19 | 9.27 | 56.39 | 2.0 | 3.0 | 2.0 | 2.0 | 3.0 |
|
||||||
|
| baseline | street_day | boulevard_skyline_day | 0.307 | 12.44 | 5.32 | 41.37 | 1.0 | 2.0 | 1.0 | 1.0 | 2.0 |
|
||||||
|
| baseline | street_night | boulevard_skyline_dusk | 0.337 | 11.94 | 5.35 | 48.34 | 2.0 | 3.0 | 2.0 | 1.0 | 2.0 |
|
||||||
|
| candidate | boulevard_day | boulevard_skyline_day | 0.113 | 20.67 | 7.8 | 42.49 | 2.0 | 3.0 | 2.0 | 2.0 | 5.0 |
|
||||||
|
| candidate | boulevard_night | hollywood_entrance_rain_night | 1.073 | 20.67 | 7.86 | 46.43 | 1.0 | 2.0 | 1.0 | 1.0 | 3.0 |
|
||||||
|
| candidate | entrance_day | hollywood_entrance_morning | 0.303 | 24.54 | 8.92 | 52.41 | 2.0 | 3.0 | 2.0 | 2.0 | 3.0 |
|
||||||
|
| candidate | street_day | boulevard_skyline_day | 0.326 | 14.54 | 5.32 | 36.3 | 1.0 | 2.0 | 1.0 | 1.0 | 3.0 |
|
||||||
|
| candidate | street_night | boulevard_skyline_dusk | 0.495 | 16.12 | 6.06 | 48.36 | 2.0 | 3.0 | 2.0 | 2.0 | 4.0 |
|
||||||
|
|
||||||
|
**baseline / boulevard_day** fixes:
|
||||||
|
- Improve the lighting model to include more realistic global illumination and soft shadows. The current lighting is too flat and harsh.
|
||||||
|
- Replace the current low-polygon character models with more detailed and anatomically correct models. Add variations in clothing and appearance.
|
||||||
|
- Increase the texture resolution and detail on all surfaces, especially buildings, roads, and foliage. Implement normal maps and specular maps for greater realism.
|
||||||
|
- Enhance the backdrop by adding more detailed building models in the distance, atmospheric perspective (haze), and a more nuanced skybox.
|
||||||
|
- Refine the material properties of all objects to better simulate real-world materials. This includes adjusting reflectivity, roughness, and subsurface scattering where appropriate.
|
||||||
|
|
||||||
|
**baseline / boulevard_night** fixes:
|
||||||
|
- Improve lighting to match the dramatic, atmospheric, and volumetric lighting of the target image, focusing on warm, directional light from the marquee and streetlamp, and realistic reflections on wet surfaces.
|
||||||
|
- Significantly upgrade material shaders to achieve realistic PBR (Physically Based Rendering) properties, especially for brick, asphalt, glass, and metal, to mimic the specular highlights, subsurface scattering, and roughness of the target.
|
||||||
|
- Increase texture resolution and detail for all surfaces, particularly the brickwork, sidewalk, and building facades, to match the fine-grained realism of the target.
|
||||||
|
- Redesign character models with higher polygon counts, more detailed textures, and realistic clothing and facial features to match the stylized but grounded look of the target's lone figure.
|
||||||
|
- Enhance the backdrop by adding atmospheric effects like fog, rain, and distant city lights, and refine the building models to have more architectural detail and variation, similar to the target's moody urban environment.
|
||||||
|
|
||||||
|
**baseline / entrance_day** fixes:
|
||||||
|
- Improve brick texture and material to match the target's depth and variation.
|
||||||
|
- Add more detailed and varied foliage, especially for the trees and bushes, to match the target's organic look.
|
||||||
|
- Enhance lighting to create softer shadows, more atmospheric haze, and warmer tones, particularly around the building entrance and street.
|
||||||
|
- Refine building details, such as window frames, signage, and architectural elements, to increase realism.
|
||||||
|
- Introduce subtle imperfections and wear on surfaces like the sidewalk and building facade to mimic real-world aging.
|
||||||
|
|
||||||
|
**baseline / street_day** fixes:
|
||||||
|
- Improve lighting to match the warm, directional sunlight and soft shadows of the target. Add volumetric fog for atmospheric perspective.
|
||||||
|
- Replace flat, untextured building models with detailed geometry and realistic materials (concrete, glass, metal).
|
||||||
|
- Add high-resolution textures to all surfaces, especially the road, sidewalk, and buildings, to mimic the detail in the target.
|
||||||
|
- Model a more detailed and varied cityscape for the backdrop, including buildings with distinct architectural styles and realistic lighting.
|
||||||
|
- Redesign character models to be more anatomically correct and have realistic clothing textures and animations.
|
||||||
|
|
||||||
|
**baseline / street_night** fixes:
|
||||||
|
- Improve lighting to match the dramatic sunset and neon glow of the target image, focusing on volumetric lighting and bloom effects.
|
||||||
|
- Enhance material properties for buildings, road, and car to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and specularity.
|
||||||
|
- Increase texture resolution and detail on all surfaces, especially the road and building facades, to mimic the fine grain and imperfections seen in the target.
|
||||||
|
- Redesign the backdrop to include a more detailed and atmospheric skyline with realistic haze and a richer sunset color gradient.
|
||||||
|
- Replace the current character models with more detailed and realistically proportioned characters that fit the urban environment.
|
||||||
|
|
||||||
|
**candidate / boulevard_day** fixes:
|
||||||
|
- Improve road and sidewalk materials to have more realistic specular highlights and surface imperfections.
|
||||||
|
- Increase texture resolution and detail on buildings, especially the brickwork, to match the target's fidelity.
|
||||||
|
- Replace stylized character models with realistic human models that have appropriate proportions and clothing.
|
||||||
|
- Enhance lighting to create softer shadows, more realistic ambient occlusion, and better global illumination, especially around the streetlights and building facades.
|
||||||
|
- Add atmospheric perspective and volumetric fog to the distant cityscape to create a more convincing sense of depth and distance haze.
|
||||||
|
|
||||||
|
**candidate / boulevard_night** fixes:
|
||||||
|
- Improve lighting to create atmospheric mood, with strong contrast and volumetric effects like fog/rain.
|
||||||
|
- Enhance material properties for realism: add specular highlights, subsurface scattering for skin, and more detailed surface imperfections.
|
||||||
|
- Increase texture resolution and detail, especially on buildings, pavement, and clothing, to match the target's gritty realism.
|
||||||
|
- Redesign characters to have more realistic proportions, clothing, and facial features, and animate them with more natural movement.
|
||||||
|
- Develop a more detailed and atmospheric backdrop, including realistic city buildings, distant haze, and appropriate weather effects.
|
||||||
|
|
||||||
|
**candidate / entrance_day** fixes:
|
||||||
|
- Improve brick texture and material to add more variation, grime, and imperfections.
|
||||||
|
- Enhance lighting to create softer shadows, more atmospheric haze, and realistic light falloff.
|
||||||
|
- Add more detail and variation to the building facade, including window frames, signage, and architectural elements.
|
||||||
|
- Replace low-poly trees and bushes with more detailed and realistic foliage models.
|
||||||
|
- Refine character models to match the realistic style of the target image, focusing on proportions, clothing, and facial features.
|
||||||
|
|
||||||
|
**candidate / street_day** fixes:
|
||||||
|
- Improve road and sidewalk materials to have more realistic specular highlights and surface imperfections.
|
||||||
|
- Add significant detail to the car models, including realistic reflections, panel gaps, and tire textures.
|
||||||
|
- Implement volumetric fog and atmospheric scattering to create a more convincing sense of depth and haze in the backdrop.
|
||||||
|
- Replace the placeholder character with a detailed, realistically lit 3D model that matches the environment's style.
|
||||||
|
- Enhance the lighting model to include softer shadows, more accurate ambient occlusion, and realistic light bounce.
|
||||||
|
|
||||||
|
**candidate / street_night** fixes:
|
||||||
|
- Improve lighting to match the dramatic, atmospheric sunset and neon glow of the target image. Add volumetric fog and bloom effects.
|
||||||
|
- Significantly enhance material properties for buildings, road, and vehicles to achieve a more realistic PBR (Physically Based Rendering) look, with appropriate reflections and surface imperfections.
|
||||||
|
- Increase texture resolution and detail on all surfaces, especially buildings and the road, to match the crispness and complexity of the target.
|
||||||
|
- Redesign the backdrop to include a more detailed and atmospheric city skyline with appropriate haze and color grading to match the sunset.
|
||||||
|
- Completely overhaul character models to be significantly more detailed, with realistic proportions, clothing, and animation, to match the quality of the target scene.
|
||||||
|
After Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 731 KiB |
|
Before Width: | Height: | Size: 891 KiB After Width: | Height: | Size: 560 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 667 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 592 KiB After Width: | Height: | Size: 756 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 956 KiB After Width: | Height: | Size: 855 KiB |
@@ -46,19 +46,19 @@ var _last_phase: int = -1
|
|||||||
## structure — phase-based lighting). Night phases switch street lamps on.
|
## structure — phase-based lighting). Night phases switch street lamps on.
|
||||||
const PHASE_LIGHTING := {
|
const PHASE_LIGHTING := {
|
||||||
Rules.Phase.EARLY: {
|
Rules.Phase.EARLY: {
|
||||||
"sun_energy": 0.62, "sun_angle": -50.0, "sun_color": Color(1.0, 0.96, 0.88),
|
"sun_energy": 0.62, "sun_angle": -34.0, "sun_yaw": -30.0, "sun_color": Color(1.0, 0.96, 0.88),
|
||||||
"sky_top": Color(0.32, 0.55, 0.95), "sky_horizon": Color(0.82, 0.88, 0.95),
|
"sky_top": Color(0.32, 0.55, 0.95), "sky_horizon": Color(0.82, 0.88, 0.95),
|
||||||
"sky_ground": Color(0.45, 0.5, 0.55), "ambient": Color(0.75, 0.78, 0.85), "night": false,
|
"sky_ground": Color(0.45, 0.5, 0.55), "ambient": Color(0.75, 0.78, 0.85), "night": false,
|
||||||
"exposure": 0.6, "glow": 0.35, "fog_color": Color(0.80, 0.85, 0.92),
|
"exposure": 0.6, "glow": 0.45, "fog_color": Color(0.87, 0.83, 0.76),
|
||||||
"fog_density": 0.0026, "fog_energy": 0.75, "contrast": 1.05, "saturation": 1.06,
|
"fog_density": 0.0034, "fog_energy": 0.5, "contrast": 1.05, "saturation": 1.04,
|
||||||
"ambient_energy": 0.1,
|
"ambient_energy": 0.1,
|
||||||
},
|
},
|
||||||
Rules.Phase.MIDDAY: {
|
Rules.Phase.MIDDAY: {
|
||||||
"sun_energy": 1.0, "sun_angle": -62.0, "sun_color": Color(1.0, 0.99, 0.96),
|
"sun_energy": 1.0, "sun_angle": -62.0, "sun_yaw": -30.0, "sun_color": Color(1.0, 0.99, 0.96),
|
||||||
"sky_top": Color(0.3, 0.52, 0.95), "sky_horizon": Color(0.85, 0.9, 0.96),
|
"sky_top": Color(0.3, 0.52, 0.95), "sky_horizon": Color(0.85, 0.9, 0.96),
|
||||||
"sky_ground": Color(0.5, 0.54, 0.58), "ambient": Color(0.85, 0.88, 0.95), "night": false,
|
"sky_ground": Color(0.5, 0.54, 0.58), "ambient": Color(0.85, 0.88, 0.95), "night": false,
|
||||||
"exposure": 0.6, "glow": 0.3, "fog_color": Color(0.84, 0.88, 0.94),
|
"exposure": 0.6, "glow": 0.4, "fog_color": Color(0.88, 0.87, 0.83),
|
||||||
"fog_density": 0.0021, "fog_energy": 0.8, "contrast": 1.06, "saturation": 1.05,
|
"fog_density": 0.0028, "fog_energy": 0.5, "contrast": 1.06, "saturation": 1.04,
|
||||||
"ambient_energy": 0.28,
|
"ambient_energy": 0.28,
|
||||||
},
|
},
|
||||||
# Evening is dusk, not a bright sunset: the sun is almost down and the street
|
# Evening is dusk, not a bright sunset: the sun is almost down and the street
|
||||||
@@ -75,7 +75,7 @@ const PHASE_LIGHTING := {
|
|||||||
"sun_energy": 0.02, "sun_angle": -8.0, "sun_color": Color(0.3, 0.35, 0.5),
|
"sun_energy": 0.02, "sun_angle": -8.0, "sun_color": Color(0.3, 0.35, 0.5),
|
||||||
"sky_top": Color(0.015, 0.02, 0.05), "sky_horizon": Color(0.06, 0.07, 0.13),
|
"sky_top": Color(0.015, 0.02, 0.05), "sky_horizon": Color(0.06, 0.07, 0.13),
|
||||||
"sky_ground": Color(0.03, 0.03, 0.05), "ambient": Color(0.06, 0.07, 0.12), "night": true,
|
"sky_ground": Color(0.03, 0.03, 0.05), "ambient": Color(0.06, 0.07, 0.12), "night": true,
|
||||||
"exposure": 0.95, "glow": 1.0, "fog_color": Color(0.08, 0.1, 0.2),
|
"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,
|
"fog_density": 0.0028, "fog_energy": 0.45, "contrast": 1.12, "saturation": 1.0,
|
||||||
"ambient_energy": 0.22,
|
"ambient_energy": 0.22,
|
||||||
},
|
},
|
||||||
@@ -190,10 +190,31 @@ func _build_guard() -> GuardController:
|
|||||||
# Aim straight ahead: tilting it down made the cone hit the ground right in
|
# 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.
|
# front of the guard instead of the person or door being checked.
|
||||||
flashlight.rotation_degrees = Vector3.ZERO
|
flashlight.rotation_degrees = Vector3.ZERO
|
||||||
flashlight.spot_range = 22.0
|
flashlight.spot_range = 16.0
|
||||||
flashlight.spot_angle = 30.0
|
flashlight.spot_angle = 20.0
|
||||||
flashlight.light_energy = 4.0
|
flashlight.light_energy = 4.0
|
||||||
flashlight.spot_attenuation = 0.6
|
flashlight.spot_attenuation = 0.8
|
||||||
|
flashlight.spot_angle_attenuation = 0.9
|
||||||
|
# Visible beam: a fading additive cone parented to the lamp, because the
|
||||||
|
# Compatibility renderer has no volumetric light. It is offset forward so the
|
||||||
|
# near cap never fills the first-person view.
|
||||||
|
var beam := MeshInstance3D.new()
|
||||||
|
beam.name = "Beam"
|
||||||
|
var cone := CylinderMesh.new()
|
||||||
|
cone.top_radius = 1.0
|
||||||
|
cone.bottom_radius = 0.05
|
||||||
|
cone.height = 5.5
|
||||||
|
cone.radial_segments = 16
|
||||||
|
cone.rings = 1
|
||||||
|
cone.cap_top = false
|
||||||
|
cone.cap_bottom = false
|
||||||
|
beam.mesh = cone
|
||||||
|
# Cylinder axis is +Y; -90° about X aims the wide end along -Z (forward).
|
||||||
|
beam.rotation_degrees = Vector3(-90, 0, 0)
|
||||||
|
beam.position = Vector3(0, 0, -3.65)
|
||||||
|
beam.material_override = Materials.light_beam()
|
||||||
|
beam.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||||
|
flashlight.add_child(beam)
|
||||||
head.add_child(flashlight)
|
head.add_child(flashlight)
|
||||||
var collider := CollisionShape3D.new()
|
var collider := CollisionShape3D.new()
|
||||||
var shape := CapsuleShape3D.new()
|
var shape := CapsuleShape3D.new()
|
||||||
@@ -484,7 +505,10 @@ func _apply_phase_lighting() -> void:
|
|||||||
if _sun:
|
if _sun:
|
||||||
_sun.light_energy = float(cfg.get("sun_energy", 1.0))
|
_sun.light_energy = float(cfg.get("sun_energy", 1.0))
|
||||||
_sun.light_color = cfg.get("sun_color", Color.WHITE)
|
_sun.light_color = cfg.get("sun_color", Color.WHITE)
|
||||||
_sun.rotation_degrees = Vector3(float(cfg.get("sun_angle", -50.0)), -30, 0)
|
# `sun_yaw` sets which side of the street the light comes from; the
|
||||||
|
# entrance view reads as shade when the sun is off the facade.
|
||||||
|
_sun.rotation_degrees = Vector3(float(cfg.get("sun_angle", -50.0)),
|
||||||
|
float(cfg.get("sun_yaw", -30.0)), 0)
|
||||||
if _sky:
|
if _sky:
|
||||||
_sky.sky_top_color = cfg.get("sky_top", Color.BLUE)
|
_sky.sky_top_color = cfg.get("sky_top", Color.BLUE)
|
||||||
_sky.sky_horizon_color = cfg.get("sky_horizon", Color.WHITE)
|
_sky.sky_horizon_color = cfg.get("sky_horizon", Color.WHITE)
|
||||||
|
|||||||
@@ -9,13 +9,19 @@ extends RefCounted
|
|||||||
## UV scaling, and texture filtering in one place so the world builder, the NPC
|
## UV scaling, and texture filtering in one place so the world builder, the NPC
|
||||||
## avatars, and the props all look like one scene.
|
## avatars, and the props all look like one scene.
|
||||||
|
|
||||||
const T_BRICK := preload("res://assets/textures/ai/game_ready/brick_facade.png")
|
## Realism-pass picks (tools/scene_concepts.py promote): brick, sidewalk pavers and
|
||||||
const T_CONCRETE := preload("res://assets/textures/ai/game_ready/concrete.png")
|
## asphalt replaced the flat first-generation tiles. Normal maps carry the mortar,
|
||||||
const T_ASPHALT := preload("res://assets/textures/ai/game_ready/asphalt.png")
|
## paver joints and aggregate that Compatibility cannot get from screen space.
|
||||||
const T_AWNING := preload("res://assets/textures/ai/game_ready/awning.png")
|
const PICK_DIR := "res://assets/textures/ai/game_ready/v2/picks/"
|
||||||
const T_BRICK_NORMAL := preload("res://assets/textures/ai/game_ready/brick_facade_normal.png")
|
const T_BRICK := preload(PICK_DIR + "brick_facade.png")
|
||||||
const T_CONCRETE_NORMAL := preload("res://assets/textures/ai/game_ready/concrete_normal.png")
|
const T_CONCRETE := preload(PICK_DIR + "sidewalk_pavers.png")
|
||||||
const T_ASPHALT_NORMAL := preload("res://assets/textures/ai/game_ready/asphalt_normal.png")
|
const T_ASPHALT := preload(PICK_DIR + "asphalt.png")
|
||||||
|
const T_ASPHALT_WET := preload(PICK_DIR + "asphalt_wet.png")
|
||||||
|
const T_AWNING := preload(PICK_DIR + "awning_canvas.png")
|
||||||
|
const T_BRICK_NORMAL := preload(PICK_DIR + "brick_facade_normal.png")
|
||||||
|
const T_CONCRETE_NORMAL := preload(PICK_DIR + "sidewalk_pavers_normal.png")
|
||||||
|
const T_ASPHALT_NORMAL := preload(PICK_DIR + "asphalt_normal.png")
|
||||||
|
const T_ASPHALT_WET_NORMAL := preload(PICK_DIR + "asphalt_wet_normal.png")
|
||||||
|
|
||||||
## Roughness / metallic pairs per surface role. Exposed so tests and tooling can
|
## Roughness / metallic pairs per surface role. Exposed so tests and tooling can
|
||||||
## assert the profiles instead of hard-coding numbers twice.
|
## assert the profiles instead of hard-coding numbers twice.
|
||||||
@@ -90,10 +96,15 @@ static func brick(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(10,
|
|||||||
static func concrete(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(9, 2.4)) -> StandardMaterial3D:
|
static func concrete(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(9, 2.4)) -> StandardMaterial3D:
|
||||||
return textured(T_CONCRETE, tint, uv_scale, "concrete", T_CONCRETE_NORMAL)
|
return textured(T_CONCRETE, tint, uv_scale, "concrete", T_CONCRETE_NORMAL)
|
||||||
|
|
||||||
static func asphalt(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(30, 7)) -> StandardMaterial3D:
|
static func asphalt(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(30, 7),
|
||||||
var mat := textured(T_ASPHALT, tint, uv_scale, "asphalt", T_ASPHALT_NORMAL)
|
wet: bool = false) -> StandardMaterial3D:
|
||||||
# Damp asphalt at night picks up the street lamps and the marquee.
|
var texture: Texture2D = T_ASPHALT_WET if wet else T_ASPHALT
|
||||||
mat.metallic_specular = 0.6
|
var normal: Texture2D = T_ASPHALT_WET_NORMAL if wet else T_ASPHALT_NORMAL
|
||||||
|
var mat := textured(texture, tint, uv_scale, "asphalt", normal)
|
||||||
|
# Damp asphalt at night picks up the street lamps and the marquee: glossier
|
||||||
|
# and slightly metallic so the lamp pools read as reflections.
|
||||||
|
mat.metallic_specular = 0.75 if wet else 0.6
|
||||||
|
mat.roughness = 0.38 if wet else float(profile("asphalt").get("roughness", 0.72))
|
||||||
return mat
|
return mat
|
||||||
|
|
||||||
static func awning(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(4, 1)) -> StandardMaterial3D:
|
static func awning(tint: Color = Color(1, 1, 1), uv_scale: Vector2 = Vector2(4, 1)) -> StandardMaterial3D:
|
||||||
@@ -117,6 +128,27 @@ static func emissive(color: Color, energy: float = 1.4) -> StandardMaterial3D:
|
|||||||
mat.roughness = 0.4
|
mat.roughness = 0.4
|
||||||
return mat
|
return mat
|
||||||
|
|
||||||
|
## Fake light shaft for the flashlight (Compatibility has no volumetrics).
|
||||||
|
## Additive, unshaded, no depth write: it glows over the scene, is still occluded
|
||||||
|
## by walls, and fades out along its length instead of ending in a hard disc.
|
||||||
|
static func light_beam() -> StandardMaterial3D:
|
||||||
|
var mat := StandardMaterial3D.new()
|
||||||
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||||
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
mat.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
|
||||||
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||||
|
mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_DISABLED
|
||||||
|
mat.albedo_color = Color(1.0, 0.95, 0.82, 1.0)
|
||||||
|
var fade := Gradient.new()
|
||||||
|
fade.set_color(0, Color(1.0, 0.95, 0.82, 0.0))
|
||||||
|
fade.add_point(0.18, Color(1.0, 0.95, 0.82, 0.22))
|
||||||
|
fade.set_color(fade.get_point_count() - 1, Color(1.0, 0.95, 0.82, 0.0))
|
||||||
|
var ramp := GradientTexture1D.new()
|
||||||
|
ramp.gradient = fade
|
||||||
|
mat.albedo_texture = ramp
|
||||||
|
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR
|
||||||
|
return mat
|
||||||
|
|
||||||
## Dark glass for windows and the entrance door. The alpha keeps the interior
|
## Dark glass for windows and the entrance door. The alpha keeps the interior
|
||||||
## visible, and the low roughness gives the facade a highlight instead of a hole.
|
## visible, and the low roughness gives the facade a highlight instead of a hole.
|
||||||
static func glass(color: Color = Color(0.42, 0.56, 0.72, 0.75)) -> StandardMaterial3D:
|
static func glass(color: Color = Color(0.42, 0.56, 0.72, 0.75)) -> StandardMaterial3D:
|
||||||
|
|||||||
@@ -2,11 +2,25 @@ class_name WorldBuilder
|
|||||||
## Procedurally assembles a minimal Hollywood Boulevard entrance prototype:
|
## Procedurally assembles a minimal Hollywood Boulevard entrance prototype:
|
||||||
|
|
||||||
const Materials := preload("res://src/game/materials.gd")
|
const Materials := preload("res://src/game/materials.gd")
|
||||||
const T_BRICK := preload("res://assets/textures/ai/game_ready/brick_facade.png")
|
const Rules := preload("res://src/core/rules.gd")
|
||||||
const T_CONCRETE := preload("res://assets/textures/ai/game_ready/concrete.png")
|
const PICK_DIR := "res://assets/textures/ai/game_ready/v2/picks/"
|
||||||
const T_ASPHALT := preload("res://assets/textures/ai/game_ready/asphalt.png")
|
const T_BRICK := preload(PICK_DIR + "brick_facade.png")
|
||||||
const T_AWNING := preload("res://assets/textures/ai/game_ready/awning.png")
|
const T_CONCRETE := preload(PICK_DIR + "sidewalk_pavers.png")
|
||||||
const T_SKYLINE := preload("res://assets/textures/skyline.png")
|
const T_ASPHALT := preload(PICK_DIR + "asphalt.png")
|
||||||
|
const T_AWNING := preload(PICK_DIR + "awning_canvas.png")
|
||||||
|
## One skyline panorama per shift phase (realism pass): hazy daylight, warm dusk
|
||||||
|
## with lit windows, and a night city. The old single strip was a black silhouette.
|
||||||
|
const T_SKYLINE_DAY := preload(PICK_DIR + "skyline_day.png")
|
||||||
|
const T_SKYLINE_DUSK := preload(PICK_DIR + "skyline_dusk.png")
|
||||||
|
const T_SKYLINE_NIGHT := preload(PICK_DIR + "skyline_night.png")
|
||||||
|
## Height of each backdrop plane (metres); the bases sit on the ground plane.
|
||||||
|
const SKYLINE_HEIGHT := 90.0
|
||||||
|
const SKYLINE_BY_PHASE := {
|
||||||
|
Rules.Phase.EARLY: {"texture": T_SKYLINE_DAY, "tint": Color(0.98, 1.0, 1.04)},
|
||||||
|
Rules.Phase.MIDDAY: {"texture": T_SKYLINE_DAY, "tint": Color(1.02, 1.04, 1.08)},
|
||||||
|
Rules.Phase.EVENING: {"texture": T_SKYLINE_DUSK, "tint": Color(0.9, 0.92, 1.0)},
|
||||||
|
Rules.Phase.LATE: {"texture": T_SKYLINE_NIGHT, "tint": Color(1.5, 1.55, 1.65)},
|
||||||
|
}
|
||||||
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
|
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
|
||||||
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street
|
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street
|
||||||
## furniture comes from the generated GLB props (tools/model_pipeline.py) with a
|
## furniture comes from the generated GLB props (tools/model_pipeline.py) with a
|
||||||
@@ -98,9 +112,18 @@ func _build_building() -> void:
|
|||||||
var building := StaticBody3D.new()
|
var building := StaticBody3D.new()
|
||||||
building.name = "Building"
|
building.name = "Building"
|
||||||
# Main facade (textured brick)
|
# Main facade (textured brick)
|
||||||
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.80, 0.76, 0.74), false, T_BRICK, Vector2(22, 10), "brick")
|
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.86, 0.85, 0.83), false, T_BRICK, Vector2(22, 10), "brick")
|
||||||
# Stone base course so the facade has a footing instead of meeting the steps dead-on.
|
# Stone base course so the facade has a footing instead of meeting the steps dead-on.
|
||||||
_add_box(building, Vector3(40, 0.8, 0.35), Vector3(0, 0.4, -3.85), Color(0.58, 0.56, 0.53), false, null, Vector2.ONE, "stone")
|
_add_box(building, Vector3(40, 0.8, 0.5), Vector3(0, 0.4, -3.78), Color(0.52, 0.5, 0.47), false, null, Vector2.ONE, "stone")
|
||||||
|
# Facade depth, not a flat wall: a projecting plinth, a string course between
|
||||||
|
# the window bands, a cornice under the roofline and pilasters at the bays.
|
||||||
|
_add_box(building, Vector3(40, 0.34, 0.22), Vector3(0, 0.93, -3.9), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
|
||||||
|
_add_box(building, Vector3(40, 0.28, 0.28), Vector3(0, 7.6, -3.86), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
|
||||||
|
_add_box(building, Vector3(40, 0.5, 0.5), Vector3(0, 17.7, -3.75), Color(0.56, 0.54, 0.51), true, null, Vector2.ONE, "stone")
|
||||||
|
_add_box(building, Vector3(40, 0.22, 0.2), Vector3(0, 10.9, -3.9), Color(0.58, 0.56, 0.53), true, null, Vector2.ONE, "stone")
|
||||||
|
for bay in [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3]:
|
||||||
|
_add_box(building, Vector3(0.5, 8.4, 0.24), Vector3(bay, 12.6, -3.9),
|
||||||
|
Color(0.8, 0.78, 0.76), true, T_BRICK, Vector2(0.3, 4.7), "brick")
|
||||||
# Entrance recess + door recess frame
|
# Entrance recess + door recess frame
|
||||||
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -3.9), Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "stone")
|
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -3.9), Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "stone")
|
||||||
# Door frame columns and lintel, so the entrance is framed rather than painted on.
|
# Door frame columns and lintel, so the entrance is framed rather than painted on.
|
||||||
@@ -151,9 +174,14 @@ func _build_facade_windows(parent: Node3D) -> void:
|
|||||||
var pane_mat := Materials.window_pane()
|
var pane_mat := Materials.window_pane()
|
||||||
pane.material_override = pane_mat
|
pane.material_override = pane_mat
|
||||||
_window_mats.append(pane_mat)
|
_window_mats.append(pane_mat)
|
||||||
# Sill below the frame.
|
# Sill below the frame and glazing bars, so the pane reads as a window
|
||||||
|
# rather than a dark hole.
|
||||||
_add_box(parent, Vector3(2.1, 0.12, 0.3), Vector3(x, y - 1.3, -3.86),
|
_add_box(parent, Vector3(2.1, 0.12, 0.3), Vector3(x, y - 1.3, -3.86),
|
||||||
Color(0.55, 0.53, 0.50), false, null, Vector2.ONE, "stone")
|
Color(0.55, 0.53, 0.50), false, null, Vector2.ONE, "stone")
|
||||||
|
_add_box(parent, Vector3(0.06, 2.2, 0.05), Vector3(x, y, -3.9),
|
||||||
|
Color(0.24, 0.23, 0.22), false, null, Vector2.ONE, "stone")
|
||||||
|
_add_box(parent, Vector3(1.7, 0.06, 0.05), Vector3(x, y + 0.25, -3.9),
|
||||||
|
Color(0.24, 0.23, 0.22), false, null, Vector2.ONE, "stone")
|
||||||
|
|
||||||
func _build_steps() -> void:
|
func _build_steps() -> void:
|
||||||
# Steps have collision so NPCs and the guard can walk up the entrance.
|
# Steps have collision so NPCs and the guard can walk up the entrance.
|
||||||
@@ -187,7 +215,7 @@ func _build_steps() -> void:
|
|||||||
func _build_sidewalk() -> void:
|
func _build_sidewalk() -> void:
|
||||||
var sidewalk := StaticBody3D.new()
|
var sidewalk := StaticBody3D.new()
|
||||||
sidewalk.name = "Sidewalk"
|
sidewalk.name = "Sidewalk"
|
||||||
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(0.74, 0.74, 0.72), false, T_CONCRETE, Vector2(9, 2.4), "concrete")
|
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(0.55, 0.55, 0.54), false, T_CONCRETE, Vector2(9, 2.4), "concrete")
|
||||||
var col := BoxShape3D.new()
|
var col := BoxShape3D.new()
|
||||||
col.size = Vector3(30, 0.36, 8)
|
col.size = Vector3(30, 0.36, 8)
|
||||||
var so := sidewalk.create_shape_owner(sidewalk)
|
var so := sidewalk.create_shape_owner(sidewalk)
|
||||||
@@ -200,7 +228,12 @@ func _build_street() -> void:
|
|||||||
street.name = "Street"
|
street.name = "Street"
|
||||||
# The roadway reads darker than the sidewalk; the raw asphalt texture tinted
|
# The roadway reads darker than the sidewalk; the raw asphalt texture tinted
|
||||||
# white looked like more pavement.
|
# white looked like more pavement.
|
||||||
_add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22), Color(0.52, 0.53, 0.57), false, T_ASPHALT, Vector2(30, 7), "asphalt")
|
# The street keeps its own material so night mode can switch to the wet
|
||||||
|
# asphalt map (glossier, with lamp reflections) and back.
|
||||||
|
_street_mat = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(30, 7))
|
||||||
|
var street_mesh := _add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22),
|
||||||
|
Color(0.62, 0.63, 0.66), false, T_ASPHALT, Vector2(30, 7), "asphalt")
|
||||||
|
street_mesh.material_override = _street_mat
|
||||||
# Center dashes along the street (x varied so they form a lane line, not a stack).
|
# Center dashes along the street (x varied so they form a lane line, not a stack).
|
||||||
for i in range(-40, 41, 4):
|
for i in range(-40, 41, 4):
|
||||||
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, 22), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
|
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, 22), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
|
||||||
@@ -239,28 +272,32 @@ func _build_skyline_backdrop() -> void:
|
|||||||
## band from the sidewalk.
|
## band from the sidewalk.
|
||||||
var backdrop := Node3D.new()
|
var backdrop := Node3D.new()
|
||||||
backdrop.name = "SkylineBackdrop"
|
backdrop.name = "SkylineBackdrop"
|
||||||
var mat := StandardMaterial3D.new()
|
# A shader, not a StandardMaterial3D: the panorama already has its own
|
||||||
mat.albedo_texture = T_SKYLINE
|
# atmospheric haze baked in, and the scene fog at 200 m washed the towers out
|
||||||
mat.texture_repeat = true
|
# to a white band (Godot 4.3 has no `fog_enabled` on StandardMaterial3D, so the
|
||||||
mat.uv1_scale = Vector3(3.0, 1.0, 1.0)
|
# `fog_disabled` render mode does the job). Two UV repeats over the 400 m ring:
|
||||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
|
# each repeat is ~200 x 90 m, close to the 2:1 panorama aspect, so the towers
|
||||||
mat.alpha_scissor_threshold = 0.5
|
# keep their proportions instead of stretching into a wide band.
|
||||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
var mat := ShaderMaterial.new()
|
||||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
var shader := Shader.new()
|
||||||
# Unshaded silhouettes read as pure black at 255-scale; lift the albedo so the
|
shader.code = SKYLINE_SHADER
|
||||||
# distant skyline sits closer to a hazy daytime city.
|
mat.shader = shader
|
||||||
mat.albedo_color = Color(2.6, 2.7, 3.0, 1.0)
|
mat.set_shader_parameter("sky_texture", SKYLINE_BY_PHASE[_phase]["texture"])
|
||||||
|
mat.set_shader_parameter("tint", SKYLINE_BY_PHASE[_phase]["tint"])
|
||||||
|
mat.set_shader_parameter("repeats", 2.0)
|
||||||
_skyline_mat = mat
|
_skyline_mat = mat
|
||||||
var mesh := PlaneMesh.new()
|
var mesh := PlaneMesh.new()
|
||||||
# PlaneMesh defaults to a horizontal (XZ) plane; the skyline must stand up.
|
# PlaneMesh defaults to a horizontal (XZ) plane; the skyline must stand up.
|
||||||
mesh.orientation = PlaneMesh.FACE_Z
|
mesh.orientation = PlaneMesh.FACE_Z
|
||||||
mesh.size = Vector2(400, 60)
|
# 90 m tall: at the 200 m ring distance the towers subtend a skyline band
|
||||||
|
# instead of a thin strip on the horizon.
|
||||||
|
mesh.size = Vector2(400, SKYLINE_HEIGHT)
|
||||||
# Far enough that the skyline reads as background and its top edge stays out
|
# Far enough that the skyline reads as background and its top edge stays out
|
||||||
# of the middle of the sky.
|
# of the middle of the sky.
|
||||||
const DIST := 200.0
|
const DIST := 200.0
|
||||||
# The texture keeps its buildings in the lower part of the image, so the plane
|
# The texture keeps its buildings in the lower part of the image, so the plane
|
||||||
# center sits at half its height: the building bases meet the ground.
|
# center sits at half its height: the building bases meet the ground.
|
||||||
const CENTER_Y := 30.0
|
const CENTER_Y := SKYLINE_HEIGHT / 2.0
|
||||||
var placements := [
|
var placements := [
|
||||||
[Vector3(0, CENTER_Y, -DIST), 0.0],
|
[Vector3(0, CENTER_Y, -DIST), 0.0],
|
||||||
[Vector3(0, CENTER_Y, DIST), 0.0],
|
[Vector3(0, CENTER_Y, DIST), 0.0],
|
||||||
@@ -277,7 +314,27 @@ func _build_skyline_backdrop() -> void:
|
|||||||
backdrop.add_child(plane)
|
backdrop.add_child(plane)
|
||||||
root.add_child(backdrop)
|
root.add_child(backdrop)
|
||||||
|
|
||||||
var _skyline_mat: StandardMaterial3D = null
|
## Backdrop shader: unshaded, alpha-scissored, and fog-free so the panorama's own
|
||||||
|
## haze shows instead of the scene fog bleaching it.
|
||||||
|
const SKYLINE_SHADER := """
|
||||||
|
shader_type spatial;
|
||||||
|
render_mode unshaded, cull_disabled, fog_disabled;
|
||||||
|
|
||||||
|
uniform sampler2D sky_texture : source_color, repeat_enable, filter_linear_mipmap;
|
||||||
|
uniform vec4 tint : source_color = vec4(1.0);
|
||||||
|
uniform float repeats = 2.0;
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec4 panorama = texture(sky_texture, vec2(UV.x * repeats, UV.y));
|
||||||
|
ALBEDO = panorama.rgb * tint.rgb;
|
||||||
|
ALPHA = panorama.a * tint.a;
|
||||||
|
ALPHA_SCISSOR_THRESHOLD = 0.5;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
var _skyline_mat: ShaderMaterial = null
|
||||||
|
var _street_mat: StandardMaterial3D = null
|
||||||
|
var _phase: int = Rules.Phase.EARLY
|
||||||
|
|
||||||
func _build_marquee() -> void:
|
func _build_marquee() -> void:
|
||||||
## Blender canopy + sign box above the door; the procedural striped awning and
|
## Blender canopy + sign box above the door; the procedural striped awning and
|
||||||
@@ -712,6 +769,26 @@ func _build_crosswalk() -> void:
|
|||||||
_add_box(root, Vector3(0.8, 0.02, 0.5), Vector3(-2.8 + i * 0.85, 0.11, 17.0), Color(0.92, 0.92, 0.92))
|
_add_box(root, Vector3(0.8, 0.02, 0.5), Vector3(-2.8 + i * 0.85, 0.11, 17.0), Color(0.92, 0.92, 0.92))
|
||||||
_add_box(root, Vector3(0.8, 0.02, 0.5), Vector3(-2.8 + i * 0.85, 0.11, 19.0), Color(0.92, 0.92, 0.92))
|
_add_box(root, Vector3(0.8, 0.02, 0.5), Vector3(-2.8 + i * 0.85, 0.11, 19.0), Color(0.92, 0.92, 0.92))
|
||||||
|
|
||||||
|
## Apply a shift phase: skyline panorama, dry/wet street, and night lighting.
|
||||||
|
## Called by GameManager on every phase change.
|
||||||
|
func set_phase(phase: int) -> void:
|
||||||
|
_phase = phase
|
||||||
|
var entry: Dictionary = SKYLINE_BY_PHASE.get(phase, SKYLINE_BY_PHASE[Rules.Phase.EARLY])
|
||||||
|
if _skyline_mat != null:
|
||||||
|
_skyline_mat.set_shader_parameter("sky_texture", entry["texture"])
|
||||||
|
_skyline_mat.set_shader_parameter("tint", entry["tint"])
|
||||||
|
var night: bool = phase == Rules.Phase.EVENING or phase == Rules.Phase.LATE
|
||||||
|
set_night_mode(night)
|
||||||
|
if _street_mat != null:
|
||||||
|
var wet: bool = phase == Rules.Phase.LATE
|
||||||
|
var dry: StandardMaterial3D = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(30, 7))
|
||||||
|
var damp: StandardMaterial3D = Materials.asphalt(Color(0.52, 0.55, 0.6), Vector2(30, 7), true)
|
||||||
|
var target: StandardMaterial3D = damp if wet else dry
|
||||||
|
_street_mat.albedo_texture = target.albedo_texture
|
||||||
|
_street_mat.normal_texture = target.normal_texture
|
||||||
|
_street_mat.roughness = target.roughness
|
||||||
|
_street_mat.metallic_specular = target.metallic_specular
|
||||||
|
|
||||||
## Switch street lamps and signage to night mode (emissive + lights on).
|
## Switch street lamps and signage to night mode (emissive + lights on).
|
||||||
func set_night_mode(night: bool) -> void:
|
func set_night_mode(night: bool) -> void:
|
||||||
for mat in _lamp_bulb_mats:
|
for mat in _lamp_bulb_mats:
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
extends SceneTree
|
||||||
|
## Dev helper for the realism pass: capture a fixed shot list of every venue at
|
||||||
|
## every shift phase, optionally with candidate textures swapped in at runtime.
|
||||||
|
## No game code changes are needed to try a new skyline or surface tile.
|
||||||
|
## Baseline and candidate runs share the same cameras, so
|
||||||
|
## `tools/scene_concepts.py realism` can compare them against the concept targets.
|
||||||
|
##
|
||||||
|
## Usage (windowed; user args go after `--`):
|
||||||
|
## godot --path . --script res://src/test/capture_realism.gd -- \
|
||||||
|
## --out=res://docs/screens/realism/<run>/baseline
|
||||||
|
## godot --path . --script res://src/test/capture_realism.gd -- \
|
||||||
|
## --out=res://docs/screens/realism/<run>/candidate \
|
||||||
|
## --swap=res://assets/textures/skyline.png=res://assets/textures/ai/game_ready/v2/skyline_day-v4.png \
|
||||||
|
## --night-swap=res://assets/textures/skyline.png=res://assets/textures/ai/game_ready/v2/skyline_night-v4.png \
|
||||||
|
## --tint=res://assets/textures/skyline.png=1,1,1 \
|
||||||
|
## --normal=res://assets/textures/ai/game_ready/brick_facade.png=res://.../brick_facade_normal.png
|
||||||
|
## # Or capture only some stages: --stages=day,late
|
||||||
|
##
|
||||||
|
## `--swap=OLD=NEW` replaces every StandardMaterial3D albedo texture loaded
|
||||||
|
## from OLD. `--tint=OLD=r,g,b` sets those materials' albedo colour.
|
||||||
|
## `--normal=OLD=MAP` enables a normal map on them. `--night-swap` applies to
|
||||||
|
## the dusk/night stages. Swaps match the *original* path, so day and night
|
||||||
|
## swaps can target the same material. Existing files in `--out` are never
|
||||||
|
## overwritten (a `-vN` suffix is added).
|
||||||
|
|
||||||
|
## Guard position/rotation per shot, matching the README capture poses.
|
||||||
|
const SHOTS_DAY := [
|
||||||
|
{"name": "entrance_day", "pos": Vector3(0, 0.1, 10.5), "yaw": 0.0, "pitch": 2.0},
|
||||||
|
{"name": "boulevard_day", "pos": Vector3(0, 0.1, 10.5), "yaw": -70.0, "pitch": 3.0},
|
||||||
|
{"name": "street_day", "pos": Vector3(0, 0.1, 10.5), "yaw": 180.0, "pitch": 2.0},
|
||||||
|
]
|
||||||
|
const SHOTS_EVENING := [
|
||||||
|
{"name": "boulevard_night", "pos": Vector3(0, 0.1, 10.5), "yaw": -70.0, "pitch": 3.0},
|
||||||
|
{"name": "street_night", "pos": Vector3(0, 0.1, 10.5), "yaw": 180.0, "pitch": 2.0},
|
||||||
|
]
|
||||||
|
const SHOTS_LATE := [
|
||||||
|
{"name": "entrance_late", "pos": Vector3(0, 0.1, 9.0), "yaw": 0.0, "pitch": 3.0},
|
||||||
|
{"name": "street_late", "pos": Vector3(0, 0.1, 10.5), "yaw": 180.0, "pitch": 2.0},
|
||||||
|
]
|
||||||
|
const SHOTS_JOKE := [
|
||||||
|
{"name": "joke_shop_door", "pos": Vector3(2.2, 0.1, 7.0), "yaw": -20.0, "pitch": 2.0},
|
||||||
|
]
|
||||||
|
const SHOTS_STUDIO := [
|
||||||
|
{"name": "studio_door", "pos": Vector3(0, 0.1, 6.2), "yaw": 0.0, "pitch": 2.0},
|
||||||
|
]
|
||||||
|
## Stage list: shift time selects the phase, `venue` switches the scenario first.
|
||||||
|
const STAGES := [
|
||||||
|
{"name": "day", "elapsed": 0.0, "venue": "", "shots": SHOTS_DAY, "night": false},
|
||||||
|
{"name": "evening", "elapsed": 620.0, "venue": "", "shots": SHOTS_EVENING, "night": true},
|
||||||
|
{"name": "late", "elapsed": 760.0, "venue": "", "shots": SHOTS_LATE, "night": true},
|
||||||
|
{"name": "joke_shop", "elapsed": 700.0, "venue": "joke_shop", "shots": SHOTS_JOKE, "night": true},
|
||||||
|
{"name": "studio", "elapsed": 700.0, "venue": "tv_studio", "shots": SHOTS_STUDIO, "night": true},
|
||||||
|
]
|
||||||
|
const SETTLE_FRAMES := 45
|
||||||
|
const MAX_FRAMES := 20000
|
||||||
|
|
||||||
|
var gm: Node = null
|
||||||
|
var frames := 0
|
||||||
|
var out_dir := "user://screenshots/realism/capture"
|
||||||
|
var swaps := {}
|
||||||
|
var night_swaps := {}
|
||||||
|
var tints := {}
|
||||||
|
var normals := {}
|
||||||
|
## material -> original albedo path, captured before the first swap.
|
||||||
|
var _origin := {}
|
||||||
|
var _stage := -1
|
||||||
|
## Optional `--stages=day,late` subset (fast iteration while tuning).
|
||||||
|
var _only_stages := {}
|
||||||
|
var _queue: Array = []
|
||||||
|
var _shot_frames := 0
|
||||||
|
var _phase := "boot"
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
root.size = Vector2i(1280, 720)
|
||||||
|
_parse_args()
|
||||||
|
var packed: PackedScene = load("res://scenes/main.tscn")
|
||||||
|
root.add_child(packed.instantiate())
|
||||||
|
|
||||||
|
func _parse_args() -> void:
|
||||||
|
for arg in OS.get_cmdline_user_args():
|
||||||
|
var text := str(arg)
|
||||||
|
if text.begins_with("--out="):
|
||||||
|
out_dir = text.substr(6)
|
||||||
|
elif text.begins_with("--night-swap="):
|
||||||
|
_pair(text.substr(13), night_swaps)
|
||||||
|
elif text.begins_with("--swap="):
|
||||||
|
_pair(text.substr(7), swaps)
|
||||||
|
elif text.begins_with("--tint="):
|
||||||
|
_pair(text.substr(7), tints)
|
||||||
|
elif text.begins_with("--normal="):
|
||||||
|
_pair(text.substr(9), normals)
|
||||||
|
elif text.begins_with("--stages="):
|
||||||
|
for name in text.substr(9).split(","):
|
||||||
|
_only_stages[name.strip_edges()] = true
|
||||||
|
|
||||||
|
func _pair(text: String, into: Dictionary) -> void:
|
||||||
|
var cut := text.find("=", 6) # skip the "res://" scheme
|
||||||
|
if cut < 0:
|
||||||
|
push_error("expected OLD=NEW, got %s" % text)
|
||||||
|
return
|
||||||
|
into[text.substr(0, cut)] = text.substr(cut + 1)
|
||||||
|
|
||||||
|
func _process(_delta: float) -> bool:
|
||||||
|
frames += 1
|
||||||
|
if frames > MAX_FRAMES:
|
||||||
|
push_error("capture_realism timed out")
|
||||||
|
quit(1)
|
||||||
|
return true
|
||||||
|
if gm == null:
|
||||||
|
gm = root.find_child("GameManager", true, false)
|
||||||
|
return false
|
||||||
|
match _phase:
|
||||||
|
"boot":
|
||||||
|
var menu = gm.get("start_menu")
|
||||||
|
if menu != null and menu.visible:
|
||||||
|
gm.call("_on_venue_selected", "hollywood_building")
|
||||||
|
_phase = "wait"
|
||||||
|
_shot_frames = 0
|
||||||
|
"wait":
|
||||||
|
_shot_frames += 1
|
||||||
|
if _shot_frames >= 20:
|
||||||
|
_phase = "stage"
|
||||||
|
"stage":
|
||||||
|
_begin_stage()
|
||||||
|
# `_begin_stage()` sets "done" after the last stage; do not clobber it.
|
||||||
|
if _phase != "done":
|
||||||
|
_phase = "shoot"
|
||||||
|
"shoot":
|
||||||
|
_run_queue()
|
||||||
|
"done":
|
||||||
|
print("capture_realism done -> %s" % out_dir)
|
||||||
|
quit(0)
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _begin_stage() -> void:
|
||||||
|
_stage += 1
|
||||||
|
while _stage < STAGES.size() and not _only_stages.is_empty() \
|
||||||
|
and not _only_stages.has(str(STAGES[_stage]["name"])):
|
||||||
|
_stage += 1
|
||||||
|
if _stage >= STAGES.size():
|
||||||
|
_phase = "done"
|
||||||
|
return
|
||||||
|
var stage: Dictionary = STAGES[_stage]
|
||||||
|
if str(stage["venue"]) != "":
|
||||||
|
gm.call("switch_scenario", str(stage["venue"]))
|
||||||
|
gm.coordinator.elapsed_s = float(stage["elapsed"])
|
||||||
|
_apply(swaps)
|
||||||
|
if bool(stage["night"]):
|
||||||
|
_apply(night_swaps)
|
||||||
|
_queue = (stage["shots"] as Array).duplicate()
|
||||||
|
_shot_frames = 0
|
||||||
|
print("capture_realism: stage %s at %d ms" % [stage["name"], Time.get_ticks_msec()])
|
||||||
|
|
||||||
|
func _run_queue() -> void:
|
||||||
|
if _queue.is_empty():
|
||||||
|
_phase = "stage"
|
||||||
|
return
|
||||||
|
var shot: Dictionary = _queue[0]
|
||||||
|
if _shot_frames == 0:
|
||||||
|
gm.guard.global_position = shot["pos"]
|
||||||
|
gm.guard.rotation.y = deg_to_rad(float(shot["yaw"]))
|
||||||
|
gm.guard.head.rotation.x = deg_to_rad(float(shot["pitch"]))
|
||||||
|
_shot_frames += 1
|
||||||
|
if _shot_frames >= SETTLE_FRAMES:
|
||||||
|
_shoot(str(shot["name"]))
|
||||||
|
_queue.pop_front()
|
||||||
|
_shot_frames = 0
|
||||||
|
|
||||||
|
## Apply swaps, tints and normals to every material whose original albedo path
|
||||||
|
## matches. Called once per stage; materials are visited through the tree.
|
||||||
|
func _apply(table: Dictionary) -> void:
|
||||||
|
var touched := 0
|
||||||
|
var by_path := {}
|
||||||
|
for node in root.find_children("*", "GeometryInstance3D", true, false):
|
||||||
|
for mat in _materials_of(node):
|
||||||
|
var original := _original_path(mat)
|
||||||
|
if original == "":
|
||||||
|
continue
|
||||||
|
if table.has(original):
|
||||||
|
var tex := load(str(table[original])) as Texture2D
|
||||||
|
if tex != null:
|
||||||
|
mat.albedo_texture = tex
|
||||||
|
touched += 1
|
||||||
|
var file := original.get_file()
|
||||||
|
by_path[file] = int(by_path.get(file, 0)) + 1
|
||||||
|
if tints.has(original):
|
||||||
|
var parts := str(tints[original]).split(",")
|
||||||
|
if parts.size() >= 3:
|
||||||
|
mat.albedo_color = Color(float(parts[0]), float(parts[1]), float(parts[2]))
|
||||||
|
if normals.has(original):
|
||||||
|
var normal_tex := load(str(normals[original])) as Texture2D
|
||||||
|
if normal_tex != null:
|
||||||
|
mat.normal_enabled = true
|
||||||
|
mat.normal_texture = normal_tex
|
||||||
|
if touched > 0:
|
||||||
|
print("capture_realism: swapped %d material(s) %s" % [touched, str(by_path)])
|
||||||
|
|
||||||
|
func _original_path(mat: StandardMaterial3D) -> String:
|
||||||
|
if not _origin.has(mat):
|
||||||
|
_origin[mat] = mat.albedo_texture.resource_path if mat.albedo_texture else ""
|
||||||
|
return str(_origin[mat])
|
||||||
|
|
||||||
|
func _materials_of(node: Node) -> Array[StandardMaterial3D]:
|
||||||
|
var out: Array[StandardMaterial3D] = []
|
||||||
|
var geo := node as GeometryInstance3D
|
||||||
|
if geo.material_override is StandardMaterial3D:
|
||||||
|
out.append(geo.material_override)
|
||||||
|
var mesh_instance := node as MeshInstance3D
|
||||||
|
if mesh_instance != null and mesh_instance.mesh != null:
|
||||||
|
for i in mesh_instance.mesh.get_surface_count():
|
||||||
|
var override := mesh_instance.get_surface_override_material(i)
|
||||||
|
if override is StandardMaterial3D:
|
||||||
|
out.append(override)
|
||||||
|
var base := mesh_instance.mesh.surface_get_material(i)
|
||||||
|
if base is StandardMaterial3D:
|
||||||
|
out.append(base)
|
||||||
|
return out
|
||||||
|
|
||||||
|
func _shoot(shot_name: String) -> void:
|
||||||
|
var dir := ProjectSettings.globalize_path(out_dir)
|
||||||
|
DirAccess.make_dir_recursive_absolute(dir)
|
||||||
|
var path := dir.path_join(shot_name + ".png")
|
||||||
|
var index := 2
|
||||||
|
while FileAccess.file_exists(path):
|
||||||
|
path = dir.path_join("%s-v%d.png" % [shot_name, index])
|
||||||
|
index += 1
|
||||||
|
var img: Image = root.get_texture().get_image()
|
||||||
|
var err := img.save_png(path)
|
||||||
|
print("%s -> %s err=%d at %d ms (fps %d)" % [shot_name, path, err,
|
||||||
|
Time.get_ticks_msec(), Engine.get_frames_per_second()])
|
||||||
@@ -30,6 +30,26 @@ func test_textured_recipes_keep_their_texture_and_profile() -> bool:
|
|||||||
var e := TestUtils.check(Materials.flat(Color.RED, "metal").metallic > 0.5, "metal role is metallic")
|
var e := TestUtils.check(Materials.flat(Color.RED, "metal").metallic > 0.5, "metal role is metallic")
|
||||||
return a and b and c and d and e
|
return a and b and c and d and e
|
||||||
|
|
||||||
|
func test_realism_pick_tiles_and_wet_asphalt() -> bool:
|
||||||
|
## The realism pass swapped the flat first-generation tiles for the v2 picks.
|
||||||
|
## The street has a dry and a wet variant for day and late night.
|
||||||
|
var brick := Materials.brick()
|
||||||
|
var dry := Materials.asphalt()
|
||||||
|
var wet := Materials.asphalt(Color(0.46, 0.48, 0.53), Vector2(30, 7), true)
|
||||||
|
var a := TestUtils.check(brick.albedo_texture.resource_path.contains("v2/picks/"),
|
||||||
|
"brick uses the promoted v2 pick")
|
||||||
|
var b := TestUtils.check(brick.normal_enabled and brick.normal_texture != null,
|
||||||
|
"brick carries its normal map")
|
||||||
|
var c := TestUtils.check(dry.albedo_texture != wet.albedo_texture,
|
||||||
|
"wet asphalt uses its own albedo")
|
||||||
|
var d := TestUtils.check(wet.roughness < dry.roughness,
|
||||||
|
"wet asphalt is glossier than dry asphalt")
|
||||||
|
var e := TestUtils.check(wet.metallic_specular > dry.metallic_specular,
|
||||||
|
"wet asphalt catches the lamps")
|
||||||
|
var f := TestUtils.check(wet.albedo_texture.resource_path.contains("picks"),
|
||||||
|
"the wet map is a promoted pick")
|
||||||
|
return a and b and c and d and e and f
|
||||||
|
|
||||||
func test_glass_and_emissive_recipes() -> bool:
|
func test_glass_and_emissive_recipes() -> bool:
|
||||||
var glass := Materials.glass()
|
var glass := Materials.glass()
|
||||||
var pane := Materials.window_pane()
|
var pane := Materials.window_pane()
|
||||||
|
|||||||
@@ -94,19 +94,28 @@ func test_steps_have_colliders() -> bool:
|
|||||||
return a and b and c
|
return a and b and c
|
||||||
|
|
||||||
func test_skyline_backdrop_uses_alpha() -> bool:
|
func test_skyline_backdrop_uses_alpha() -> bool:
|
||||||
## The opaque skyline texture made the plane render as a black slab (TODO).
|
## The old opaque skyline texture made the plane render as a black slab, and
|
||||||
|
## the scene fog bleached the panorama into a white band. The backdrop is an
|
||||||
|
## alpha-scissored, fog-free shader with a panorama per phase.
|
||||||
var root := Node3D.new()
|
var root := Node3D.new()
|
||||||
var builder := WorldBuilder.new(root)
|
var builder := WorldBuilder.new(root)
|
||||||
builder.build()
|
builder.build()
|
||||||
var backdrop := root.find_child("SkylineBackdrop", true, false)
|
var backdrop := root.find_child("SkylineBackdrop", true, false)
|
||||||
var a := TestUtils.check(backdrop != null, "SkylineBackdrop exists")
|
var a := TestUtils.check(backdrop != null, "SkylineBackdrop exists")
|
||||||
var mat: StandardMaterial3D = builder._skyline_mat
|
var mat: ShaderMaterial = builder._skyline_mat
|
||||||
var b := TestUtils.check(mat != null, "skyline material captured")
|
var b := TestUtils.check(mat != null and mat.shader != null, "skyline shader material captured")
|
||||||
var c := TestUtils.check(
|
var code := mat.shader.code if mat != null and mat.shader != null else ""
|
||||||
mat != null and mat.transparency != BaseMaterial3D.TRANSPARENCY_DISABLED,
|
var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled"),
|
||||||
"skyline material uses alpha, not an opaque slab")
|
"backdrop shader is alpha-scissored and fog-free")
|
||||||
|
var day: Texture2D = mat.get_shader_parameter("sky_texture")
|
||||||
|
builder.set_phase(Rules.Phase.LATE)
|
||||||
|
var night: Texture2D = mat.get_shader_parameter("sky_texture")
|
||||||
|
var d := TestUtils.check(day != null and night != null and day != night,
|
||||||
|
"the skyline panorama follows the shift phase")
|
||||||
|
var e := TestUtils.check(builder._street_mat != null
|
||||||
|
and builder._street_mat.roughness < 0.5, "the night street turns wet")
|
||||||
root.free()
|
root.free()
|
||||||
return a and b and c
|
return a and b and c and d and e
|
||||||
|
|
||||||
func test_door_indicator_built() -> bool:
|
func test_door_indicator_built() -> bool:
|
||||||
var root := Node3D.new()
|
var root := Node3D.new()
|
||||||
|
|||||||
@@ -1056,20 +1056,37 @@ def build_gear(name):
|
|||||||
# Props (grounded, front = Blender -Y)
|
# Props (grounded, front = Blender -Y)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _shrub(name, center, radii, mat, lumps=7, seed=1):
|
def _shrub(name, center, radii, mat, lumps=7, seed=1, lump=0.42):
|
||||||
"""Lumpy foliage cluster from overlapping low-poly spheres."""
|
"""Dense foliage cluster: many small flat-shaded clumps, not one smooth blob.
|
||||||
|
|
||||||
|
Reads as leaves at game distance because the silhouette is broken up and the
|
||||||
|
facets catch the light, which is what the realism-pass foliage note asks for.
|
||||||
|
"""
|
||||||
parts = []
|
parts = []
|
||||||
import random
|
import random
|
||||||
rng = random.Random(seed)
|
rng = random.Random(seed)
|
||||||
parts.append(sphere(name, 1.0, center, mat, seg=10, rings=7, scale=radii))
|
parts.append(sphere(name, 1.0, center, mat, seg=10, rings=7, scale=radii,
|
||||||
for i in range(lumps):
|
smooth=False))
|
||||||
a = math.tau * i / lumps + rng.uniform(-0.3, 0.3)
|
for i in range(lumps * 2): # inner clumps fill the volume
|
||||||
r = rng.uniform(0.45, 0.6)
|
a = math.tau * i / (lumps * 2) + rng.uniform(-0.25, 0.25)
|
||||||
off = (center[0] + math.cos(a) * radii[0] * 0.6,
|
ring = rng.uniform(0.25, 0.75)
|
||||||
center[1] + math.sin(a) * radii[1] * 0.6,
|
r = rng.uniform(lump * 0.8, lump * 1.25)
|
||||||
center[2] + rng.uniform(-0.1, 0.35) * radii[2])
|
off = (center[0] + math.cos(a) * radii[0] * ring,
|
||||||
|
center[1] + math.sin(a) * radii[1] * ring,
|
||||||
|
center[2] + rng.uniform(-0.2, 0.55) * radii[2])
|
||||||
parts.append(sphere(name, 1.0, off, mat, seg=8, rings=6,
|
parts.append(sphere(name, 1.0, off, mat, seg=8, rings=6,
|
||||||
scale=(radii[0] * r, radii[1] * r, radii[2] * r * 0.9)))
|
scale=(radii[0] * r, radii[1] * r, radii[2] * r * 0.9),
|
||||||
|
smooth=False))
|
||||||
|
for i in range(max(3, lumps // 2)): # outer clumps break the silhouette
|
||||||
|
a = math.tau * i / max(3, lumps // 2) + rng.uniform(-0.3, 0.3)
|
||||||
|
r = rng.uniform(lump * 0.6, lump * 0.85)
|
||||||
|
off = (center[0] + math.cos(a) * radii[0] * rng.uniform(0.85, 1.0),
|
||||||
|
center[1] + math.sin(a) * radii[1] * rng.uniform(0.85, 1.0),
|
||||||
|
center[2] + rng.uniform(-0.05, 0.4) * radii[2])
|
||||||
|
parts.append(sphere(name, 1.0, off, mat, seg=8, rings=6,
|
||||||
|
scale=(radii[0] * r, radii[1] * r, radii[2] * r * 0.8),
|
||||||
|
rot=(rng.uniform(0, 1), rng.uniform(0, 1), rng.uniform(0, 3)),
|
||||||
|
smooth=False))
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
|
|
||||||
@@ -1115,7 +1132,7 @@ def build_prop(name):
|
|||||||
bevel=0.025, segs=2))
|
bevel=0.025, segs=2))
|
||||||
p.append(box("Lip", (1.0, 1.0, 0.05), (0, 0, 0.515), M("stone_pot"), bevel=0.015))
|
p.append(box("Lip", (1.0, 1.0, 0.05), (0, 0, 0.515), M("stone_pot"), bevel=0.015))
|
||||||
p.append(box("Soil", (0.86, 0.86, 0.02), (0, 0, 0.535), M("soil")))
|
p.append(box("Soil", (0.86, 0.86, 0.02), (0, 0, 0.535), M("soil")))
|
||||||
p += _shrub("Shrub", (0, 0, 0.78), (0.42, 0.42, 0.30), M("foliage"), lumps=8, seed=3)
|
p += _shrub("Shrub", (0, 0, 0.78), (0.42, 0.42, 0.30), M("foliage"), lumps=5, seed=3)
|
||||||
elif name == "newspaper_box": # 0.44 x 0.38, h ~0.92
|
elif name == "newspaper_box": # 0.44 x 0.38, h ~0.92
|
||||||
p.append(box("Stand", (0.40, 0.34, 0.10), (0, 0, 0.05), M("news_blue"), bevel=0.01))
|
p.append(box("Stand", (0.40, 0.34, 0.10), (0, 0, 0.05), M("news_blue"), bevel=0.01))
|
||||||
p.append(box("Body", (0.42, 0.34, 0.70), (0, 0, 0.45), M("news_blue"), bevel=0.02))
|
p.append(box("Body", (0.42, 0.34, 0.70), (0, 0, 0.45), M("news_blue"), bevel=0.02))
|
||||||
@@ -1159,9 +1176,9 @@ def build_prop(name):
|
|||||||
M("bark"), seg=8))
|
M("bark"), seg=8))
|
||||||
p.append(tube("Branch", [(0, 0, 1.6), (0.35, 0.1, 2.2)], 0.04, M("bark"), seg=6))
|
p.append(tube("Branch", [(0, 0, 1.6), (0.35, 0.1, 2.2)], 0.04, M("bark"), seg=6))
|
||||||
p.append(tube("Branch", [(0, 0, 1.7), (-0.3, -0.2, 2.3)], 0.04, M("bark"), seg=6))
|
p.append(tube("Branch", [(0, 0, 1.7), (-0.3, -0.2, 2.3)], 0.04, M("bark"), seg=6))
|
||||||
p += _shrub("Crown", (0, 0, 2.55), (0.95, 0.95, 0.85), M("foliage"), lumps=9, seed=7)
|
p += _shrub("Crown", (0, 0, 2.55), (0.95, 0.95, 0.85), M("foliage"), lumps=7, seed=7)
|
||||||
p += _shrub("CrownTop", (0.05, 0.05, 3.15), (0.6, 0.6, 0.5), M("foliage_dark"),
|
p += _shrub("CrownTop", (0.05, 0.05, 3.1), (0.62, 0.62, 0.52), M("foliage_dark"),
|
||||||
lumps=5, seed=11)
|
lumps=4, seed=11)
|
||||||
p.append(box("Grate", (1.0, 1.0, 0.02), (0, 0, 0.01), M("dark_metal")))
|
p.append(box("Grate", (1.0, 1.0, 0.02), (0, 0, 0.01), M("dark_metal")))
|
||||||
elif name == "palm_tree": # ~7 m Hollywood palm
|
elif name == "palm_tree": # ~7 m Hollywood palm
|
||||||
prof = []
|
prof = []
|
||||||
@@ -1200,7 +1217,7 @@ def build_prop(name):
|
|||||||
p.append(lathe("Pot", [(0.0, 0.0), (0.22, 0.0), (0.26, 0.05), (0.33, 0.50),
|
p.append(lathe("Pot", [(0.0, 0.0), (0.22, 0.0), (0.26, 0.05), (0.33, 0.50),
|
||||||
(0.36, 0.52), (0.36, 0.58), (0.31, 0.58), (0.0, 0.56)],
|
(0.36, 0.52), (0.36, 0.58), (0.31, 0.58), (0.0, 0.56)],
|
||||||
M("stone_pot"), seg=20, sharp=30))
|
M("stone_pot"), seg=20, sharp=30))
|
||||||
p += _shrub("Topiary", (0, 0, 0.82), (0.34, 0.34, 0.30), M("foliage"), lumps=7, seed=5)
|
p += _shrub("Topiary", (0, 0, 0.82), (0.34, 0.34, 0.30), M("foliage"), lumps=4, seed=5)
|
||||||
elif name == "a_frame_sign": # chalkboard sidewalk sign
|
elif name == "a_frame_sign": # chalkboard sidewalk sign
|
||||||
for s in (-1.0, 1.0):
|
for s in (-1.0, 1.0):
|
||||||
tilt = math.radians(14) * s
|
tilt = math.radians(14) * s
|
||||||
|
|||||||
@@ -0,0 +1,982 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Realism pass: AI scene references, skyline backdrops and surface textures.
|
||||||
|
|
||||||
|
Generates example 3D-game scene frames (art direction), realistic skyline
|
||||||
|
panoramas that replace the outdated flat `assets/textures/skyline.png`, and
|
||||||
|
higher-detail seamless surface tiles; processes them into game-ready
|
||||||
|
candidates; and scores in-game captures against the realistic target.
|
||||||
|
|
||||||
|
**No command ever overwrites an existing image.** When a target path exists the
|
||||||
|
next free `-vN` name is used (generate) or the step is skipped (process).
|
||||||
|
|
||||||
|
python3 tools/scene_concepts.py list
|
||||||
|
python3 tools/scene_concepts.py generate [--group scene|backdrop|texture]
|
||||||
|
[--only name,...] [--variants N]
|
||||||
|
python3 tools/scene_concepts.py process # raw -> game_ready/v2 (+ normals)
|
||||||
|
python3 tools/scene_concepts.py promote # validated picks -> picks/<name>.png
|
||||||
|
python3 tools/scene_concepts.py audit # asset_pipeline checks on v2
|
||||||
|
python3 tools/scene_concepts.py realism --run docs/screens/realism/<run>
|
||||||
|
[--judge] [--model ...]
|
||||||
|
|
||||||
|
Paths:
|
||||||
|
raw AI output assets/textures/ai/v2/raw/<group>/<name>.png (gitignored)
|
||||||
|
game-ready candidates assets/textures/ai/game_ready/v2/<name>.png (+ _normal)
|
||||||
|
scene references docs/screens/concepts/<name>.png
|
||||||
|
trial captures/reports docs/screens/realism/<run>/{baseline,candidate}/ + report
|
||||||
|
|
||||||
|
The API key comes from OPENROUTER_API_KEY or the repo `.env`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image, ImageFilter, ImageOps, ImageStat
|
||||||
|
|
||||||
|
import asset_pipeline
|
||||||
|
import generate_textures
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
RAW_DIR = ROOT / "assets" / "textures" / "ai" / "v2" / "raw"
|
||||||
|
READY_DIR = ROOT / "assets" / "textures" / "ai" / "game_ready" / "v2"
|
||||||
|
CONCEPT_DIR = ROOT / "docs" / "screens" / "concepts"
|
||||||
|
REALISM_DIR = ROOT / "docs" / "screens" / "realism"
|
||||||
|
|
||||||
|
DEFAULT_MODEL = "openai/gpt-5-image-mini"
|
||||||
|
DEFAULT_JUDGE_MODEL = "google/gemini-2.5-flash-lite"
|
||||||
|
OPENROUTER_CHAT_URL = "https://openrouter.ai/api/v1/chat/completions"
|
||||||
|
|
||||||
|
SCENE_W, SCENE_H = 1280, 720
|
||||||
|
# 2:1 matches one UV repeat of the backdrop ring (400 m x 60 m plane, 3 repeats
|
||||||
|
# = ~133 m x 60 m per tile), so buildings are not stretched vertically.
|
||||||
|
BACKDROP_W, BACKDROP_H = 2048, 1024
|
||||||
|
TILE = 1024
|
||||||
|
|
||||||
|
SCENE_STYLE = (
|
||||||
|
"screenshot from a modern realistic 3D video game rendered in a real-time "
|
||||||
|
"engine, eye-level first-person camera at 1.7 m, physically based materials, "
|
||||||
|
"believable global illumination and soft shadows, atmospheric haze for depth, "
|
||||||
|
"high texture detail, no HUD, no text overlays, no watermark, 16:9 widescreen")
|
||||||
|
BACKDROP_STYLE = (
|
||||||
|
"wide straight-on orthographic-looking panorama for a game skybox backdrop: "
|
||||||
|
"the city silhouette sits in a narrow horizontal band across the middle of the "
|
||||||
|
"image, flat level horizon, no foreground street, no people, no vehicles, "
|
||||||
|
"fully transparent empty sky above the buildings, the bottom edge is a solid "
|
||||||
|
"continuous band of low buildings and trees with no gaps, absolutely no "
|
||||||
|
"lettering, no signs, no Hollywood sign, no text, no watermark")
|
||||||
|
TEXTURE_STYLE = (
|
||||||
|
"seamless tileable PBR albedo texture, photographed straight-on and "
|
||||||
|
"orthographic, flat even diffuse lighting with no cast shadows or highlights, "
|
||||||
|
"uniform scale across the whole image, no perspective, no vignette, no text")
|
||||||
|
|
||||||
|
# name -> (group, prompt). Scene names describe venue + time of day.
|
||||||
|
CATALOGUE = {
|
||||||
|
# -- Example 3D game scenes (art direction) ------------------------------
|
||||||
|
"hollywood_entrance_morning": ("scene", (
|
||||||
|
"Hollywood Boulevard brick building entrance in early morning sun, short "
|
||||||
|
"concrete steps up to a dark glazed double door under a bulb-lit marquee, "
|
||||||
|
"black bollards, a green litter bin, potted shrubs, palm trees and hazy "
|
||||||
|
"mid-rise buildings down the boulevard")),
|
||||||
|
"hollywood_entrance_golden_hour": ("scene", (
|
||||||
|
"the same Hollywood brick venue entrance at golden hour, long warm shadows "
|
||||||
|
"across worn concrete sidewalk pavers, marquee bulbs starting to glow, a few "
|
||||||
|
"pedestrians, palm silhouettes against an orange sky")),
|
||||||
|
"hollywood_entrance_rain_night": ("scene", (
|
||||||
|
"Hollywood brick venue entrance late at night in light rain, wet asphalt "
|
||||||
|
"reflecting the marquee and sodium street lamps, puddles on the sidewalk, "
|
||||||
|
"lit windows in the distant skyline, a lone security guard post by the door")),
|
||||||
|
"boulevard_skyline_day": ("scene", (
|
||||||
|
"view from a Hollywood Boulevard sidewalk across a four-lane street toward a "
|
||||||
|
"layered Los Angeles skyline of mid-rise and high-rise buildings, palm trees "
|
||||||
|
"lining the street, the Hollywood hills hazy on the horizon, clear blue sky")),
|
||||||
|
"boulevard_skyline_dusk": ("scene", (
|
||||||
|
"Hollywood Boulevard at dusk looking down the street, street lamps switching "
|
||||||
|
"on, purple and orange sky, distant skyline windows lighting up, car light "
|
||||||
|
"trails, palm trees in silhouette")),
|
||||||
|
"joke_shop_alley_evening": ("scene", (
|
||||||
|
"a narrow brick side street outside a small comedy club in the evening, a red "
|
||||||
|
"scalloped awning over a wooden door, a warm wall lantern, a chalkboard "
|
||||||
|
"A-frame sign on the sidewalk, a short queue of people in jackets")),
|
||||||
|
"joke_shop_door_closeup": ("scene", (
|
||||||
|
"close view of a comedy club door: aged dark-red brick with mortar detail, a "
|
||||||
|
"brass door handle, a glowing wall lantern, a chalkboard sign, evening light")),
|
||||||
|
"studio_stage_door_night": ("scene", (
|
||||||
|
"a TV studio stage door at night: granite facade, a large bulb-lit marquee "
|
||||||
|
"canopy, a black sedan at the curb, chrome stanchions with a red velvet rope, "
|
||||||
|
"fans with phones behind a steel crowd barrier")),
|
||||||
|
"sidewalk_material_closeup": ("scene", (
|
||||||
|
"low camera close-up along a city sidewalk: weathered concrete pavers with "
|
||||||
|
"expansion joints and stains, a granite curb, asphalt with aggregate and lane "
|
||||||
|
"paint, a cast-iron tree grate, morning sun")),
|
||||||
|
"entrance_steps_podium": ("scene", (
|
||||||
|
"a security check-in podium at the foot of three granite entrance steps with "
|
||||||
|
"black metal handrails, glass double doors with a card reader, a bulb marquee "
|
||||||
|
"above, daylight")),
|
||||||
|
"crowd_barrier_line": ("scene", (
|
||||||
|
"a row of steel bike-rack crowd barriers along a Hollywood sidewalk before a "
|
||||||
|
"premiere, people waiting behind them, a red carpet, palm trees and a hazy "
|
||||||
|
"skyline, late afternoon")),
|
||||||
|
"responder_arrival_wide": ("scene", (
|
||||||
|
"wide shot of a fire engine and firefighters in dark turnout gear arriving at "
|
||||||
|
"a brick venue entrance at night, red and blue light reflections on wet "
|
||||||
|
"pavement, onlookers kept back by a guard")),
|
||||||
|
# -- Skyline backdrops (replace assets/textures/skyline.png) -------------
|
||||||
|
"skyline_day": ("backdrop", (
|
||||||
|
"Los Angeles Hollywood skyline in daylight: varied mid-rise and high-rise "
|
||||||
|
"buildings with realistic facades and windows, a few palm tree silhouettes, "
|
||||||
|
"soft blue atmospheric haze, low green-brown hills far behind")),
|
||||||
|
"skyline_dusk": ("backdrop", (
|
||||||
|
"Los Angeles Hollywood skyline just after sunset: neutral grey-blue building "
|
||||||
|
"facades, scattered warm lit windows, palm silhouettes, deep violet-blue sky "
|
||||||
|
"haze, low hills far behind, no red or orange floodlighting, no neon signs")),
|
||||||
|
"skyline_night": ("backdrop", (
|
||||||
|
"Los Angeles Hollywood skyline at night: dark building masses with many warm "
|
||||||
|
"and cool lit windows, rooftop red aviation lights, palm silhouettes, faint "
|
||||||
|
"city glow on the haze")),
|
||||||
|
# -- Surface tiles (replace / extend the game_ready textures) ------------
|
||||||
|
"brick_facade": ("texture", (
|
||||||
|
"aged red-brown clay brick wall in running bond, recessed grey mortar joints, "
|
||||||
|
"slight colour variation between bricks, small chips and soot, 12 courses")),
|
||||||
|
"granite_facade": ("texture", (
|
||||||
|
"polished grey speckled granite wall cladding in large rectangular panels "
|
||||||
|
"with thin dark joints")),
|
||||||
|
"sidewalk_pavers": ("texture", (
|
||||||
|
"weathered light-grey city sidewalk concrete in 1.2 m square slabs with saw-cut "
|
||||||
|
"joints, faint stains, fine aggregate and hairline cracks, top-down")),
|
||||||
|
"asphalt": ("texture", (
|
||||||
|
"dry dark-grey road asphalt with visible aggregate stones, slight wear, "
|
||||||
|
"top-down, no lane markings")),
|
||||||
|
"asphalt_wet": ("texture", (
|
||||||
|
"wet dark road asphalt at night with small damp patches and glossy aggregate, "
|
||||||
|
"top-down, no lane markings, no reflections of objects")),
|
||||||
|
"stone_steps": ("texture", (
|
||||||
|
"light grey limestone step surface with fine pitting and worn edges, top-down")),
|
||||||
|
"awning_canvas": ("texture", (
|
||||||
|
"outdoor awning canvas with broad vertical burgundy and cream stripes, "
|
||||||
|
"visible woven fabric weave, slight weathering")),
|
||||||
|
"stucco_wall": ("texture", (
|
||||||
|
"warm beige exterior stucco wall with fine sand-float texture and subtle "
|
||||||
|
"weathering")),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validated picks promoted to stable paths by `promote`. The name on the left is
|
||||||
|
# what the game loads (`assets/textures/ai/game_ready/v2/picks/<name>.png`); the
|
||||||
|
# value is the candidate in READY_DIR it came from. Promoted files are tracked
|
||||||
|
# and never overwritten: a re-promote writes the next free `-vN` name.
|
||||||
|
PICKS = {
|
||||||
|
"skyline_day": "skyline_day-v4",
|
||||||
|
"skyline_dusk": "skyline_dusk-v5",
|
||||||
|
"skyline_night": "skyline_night-v4",
|
||||||
|
"brick_facade": "brick_facade",
|
||||||
|
"sidewalk_pavers": "sidewalk_pavers",
|
||||||
|
"asphalt": "asphalt",
|
||||||
|
"asphalt_wet": "asphalt_wet",
|
||||||
|
"awning_canvas": "awning_canvas-v3",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Realism trial: capture shot -> concept it is judged against.
|
||||||
|
SHOTS = {
|
||||||
|
"entrance_day": "hollywood_entrance_morning",
|
||||||
|
"boulevard_day": "boulevard_skyline_day",
|
||||||
|
"street_day": "boulevard_skyline_day",
|
||||||
|
"boulevard_night": "hollywood_entrance_rain_night",
|
||||||
|
"street_night": "boulevard_skyline_dusk",
|
||||||
|
"entrance_late": "hollywood_entrance_rain_night",
|
||||||
|
"street_late": "boulevard_skyline_dusk",
|
||||||
|
# The venues share the Hollywood geometry for now, so these two compare
|
||||||
|
# lighting and atmosphere rather than layout (noted in TODO.md).
|
||||||
|
"joke_shop_door": "joke_shop_door_closeup",
|
||||||
|
"studio_door": "studio_stage_door_night",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Paths (never overwrite)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def next_free_path(path: Path) -> Path:
|
||||||
|
"""Return `path` if unused, else the first unused `<stem>-vN<suffix>`."""
|
||||||
|
path = Path(path)
|
||||||
|
if not path.exists():
|
||||||
|
return path
|
||||||
|
index = 2
|
||||||
|
while True:
|
||||||
|
candidate = path.with_name(f"{path.stem}-v{index}{path.suffix}")
|
||||||
|
if not candidate.exists():
|
||||||
|
return candidate
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
|
||||||
|
def raw_path(name: str) -> Path:
|
||||||
|
group = CATALOGUE[name][0]
|
||||||
|
if group == "scene":
|
||||||
|
return CONCEPT_DIR / f"{name}.png"
|
||||||
|
return RAW_DIR / group / f"{name}.png"
|
||||||
|
|
||||||
|
|
||||||
|
def raw_variants(name: str):
|
||||||
|
"""All raw files generated for `name` (base + -vN), oldest first."""
|
||||||
|
base = raw_path(name)
|
||||||
|
found = [base] if base.is_file() else []
|
||||||
|
found += sorted(base.parent.glob(f"{base.stem}-v*{base.suffix}"),
|
||||||
|
key=lambda p: int(p.stem.rsplit("-v", 1)[1])
|
||||||
|
if p.stem.rsplit("-v", 1)[1].isdigit() else 0)
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def _pixels(image: Image.Image) -> list:
|
||||||
|
"""Flat pixel list without the Pillow 12 `getdata` deprecation warning."""
|
||||||
|
getter = getattr(image, "get_flattened_data", None)
|
||||||
|
return list(getter() if getter else image.getdata())
|
||||||
|
|
||||||
|
|
||||||
|
def _rel(path: Path) -> str:
|
||||||
|
path = Path(path)
|
||||||
|
return str(path.relative_to(ROOT)) if path.is_relative_to(ROOT) else str(path)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_env_file(path=None):
|
||||||
|
path = Path(path) if path else ROOT / ".env"
|
||||||
|
if not path.is_file():
|
||||||
|
return
|
||||||
|
for raw in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = raw.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
os.environ.setdefault(key.strip(), value.strip().strip("\"'"))
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Generate
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def compose_prompt(name: str) -> str:
|
||||||
|
group, prompt = CATALOGUE[name]
|
||||||
|
style = {"scene": SCENE_STYLE, "backdrop": BACKDROP_STYLE,
|
||||||
|
"texture": TEXTURE_STYLE}[group]
|
||||||
|
return f"{prompt}. {style}."
|
||||||
|
|
||||||
|
|
||||||
|
def generate_one(name: str, model: str, request_size: int) -> Path:
|
||||||
|
group = CATALOGUE[name][0]
|
||||||
|
prompt = compose_prompt(name)
|
||||||
|
if group == "scene":
|
||||||
|
image = generate_textures.generate_image(prompt, model, request_size,
|
||||||
|
transparent=False, aspect_ratio="3:2")
|
||||||
|
image = ImageOps.fit(image.convert("RGB"), (SCENE_W, SCENE_H),
|
||||||
|
method=Image.Resampling.LANCZOS)
|
||||||
|
elif group == "backdrop":
|
||||||
|
image = generate_textures.generate_image(prompt, model, request_size,
|
||||||
|
transparent=True, aspect_ratio="3:2")
|
||||||
|
else:
|
||||||
|
image = generate_textures.generate_image(prompt, model, request_size,
|
||||||
|
transparent=False, aspect_ratio="1:1")
|
||||||
|
image = image.convert("RGB")
|
||||||
|
out = next_free_path(raw_path(name))
|
||||||
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
image.save(out)
|
||||||
|
print(f"wrote {_rel(out)} {image.size}")
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_generate(args) -> int:
|
||||||
|
_load_env_file()
|
||||||
|
names = _select(args.group, args.only)
|
||||||
|
if not names:
|
||||||
|
print("nothing selected", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
failed = []
|
||||||
|
for name in names:
|
||||||
|
for _ in range(max(1, args.variants)):
|
||||||
|
print(f"generating {name} ...", flush=True)
|
||||||
|
try:
|
||||||
|
generate_one(name, args.model, args.request_size)
|
||||||
|
except SystemExit as exc: # generate_textures exits on API errors
|
||||||
|
failed.append(name)
|
||||||
|
print(f" FAILED {name}: {exc}", file=sys.stderr)
|
||||||
|
break
|
||||||
|
if failed:
|
||||||
|
print("failed: " + ", ".join(failed), file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _select(group, only):
|
||||||
|
wanted = {n.strip() for n in (only or "").split(",") if n.strip()}
|
||||||
|
return [n for n, (g, _) in CATALOGUE.items()
|
||||||
|
if (not group or g == group) and (not wanted or n in wanted)]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Process (raw -> game-ready candidates)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def key_sky(image: Image.Image, threshold: float = 34.0) -> Image.Image:
|
||||||
|
"""Make the sky transparent when the model returned an opaque image.
|
||||||
|
|
||||||
|
Walks every column from the top while pixels stay close to a running sky
|
||||||
|
colour (follows vertical gradients); everything above the first building
|
||||||
|
pixel becomes transparent.
|
||||||
|
"""
|
||||||
|
rgba = image.convert("RGBA")
|
||||||
|
if asset_pipeline.has_transparency(rgba):
|
||||||
|
return rgba
|
||||||
|
width, height = rgba.size
|
||||||
|
px = rgba.load()
|
||||||
|
for x in range(width):
|
||||||
|
seed = [px[x, y] for y in range(min(6, height))]
|
||||||
|
sky = [sum(c[i] for c in seed) / len(seed) for i in range(3)]
|
||||||
|
for y in range(height):
|
||||||
|
r, g, b, _ = px[x, y]
|
||||||
|
dist = math.sqrt((r - sky[0]) ** 2 + (g - sky[1]) ** 2 + (b - sky[2]) ** 2)
|
||||||
|
if dist > threshold:
|
||||||
|
break
|
||||||
|
sky = [sky[0] * 0.9 + r * 0.1, sky[1] * 0.9 + g * 0.1, sky[2] * 0.9 + b * 0.1]
|
||||||
|
px[x, y] = (r, g, b, 0)
|
||||||
|
return rgba
|
||||||
|
|
||||||
|
|
||||||
|
def _band(image: Image.Image, aspect: float = 2.0) -> Image.Image:
|
||||||
|
"""Crop to the skyline's alpha bounds, bottom-aligned on an `aspect` canvas.
|
||||||
|
|
||||||
|
Tower tops are never cropped: when the content is shorter than the
|
||||||
|
target aspect, transparent sky is added above it.
|
||||||
|
"""
|
||||||
|
width, height = image.size
|
||||||
|
bbox = image.getchannel("A").point(lambda a: 255 if a >= 128 else 0).getbbox()
|
||||||
|
top, bottom = (bbox[1], bbox[3]) if bbox else (0, height)
|
||||||
|
content = image.crop((0, top, width, bottom))
|
||||||
|
canvas_h = max(content.size[1], int(round(width / aspect)))
|
||||||
|
canvas = Image.new("RGBA", (width, canvas_h), (0, 0, 0, 0))
|
||||||
|
canvas.paste(content, (0, canvas_h - content.size[1]))
|
||||||
|
return canvas
|
||||||
|
|
||||||
|
|
||||||
|
def tile_horizontally(image: Image.Image, band: int) -> Image.Image:
|
||||||
|
"""Crossfade the right edge into the left so the strip repeats seamlessly."""
|
||||||
|
width, height = image.size
|
||||||
|
band = max(1, min(band, width // 4))
|
||||||
|
out = image.crop((0, 0, width - band, height)).copy()
|
||||||
|
left = image.crop((0, 0, band, height))
|
||||||
|
right = image.crop((width - band, 0, width, height))
|
||||||
|
mask = Image.linear_gradient("L").rotate(90, expand=True).resize((band, height))
|
||||||
|
# mask: 255 at x=0 (take the right edge), fading to 0 (take the left edge)
|
||||||
|
mixed = Image.composite(right, left, ImageOps.mirror(mask))
|
||||||
|
out.paste(mixed, (0, 0))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def process_backdrop(source: Path, target: Path) -> Path:
|
||||||
|
with Image.open(source) as image:
|
||||||
|
image.load()
|
||||||
|
rgba = key_sky(image)
|
||||||
|
strip = _band(rgba)
|
||||||
|
strip = tile_horizontally(strip, band=max(16, strip.size[0] // 10))
|
||||||
|
strip = strip.resize((BACKDROP_W, BACKDROP_H), Image.Resampling.LANCZOS)
|
||||||
|
# Alpha-scissor material: hard alpha, and a solid base so no sky shows
|
||||||
|
# through gaps at the street line.
|
||||||
|
alpha = strip.getchannel("A").point(lambda a: 255 if a >= 128 else 0)
|
||||||
|
base = int(BACKDROP_H * 0.9)
|
||||||
|
alpha.paste(255, (0, base, BACKDROP_W, BACKDROP_H))
|
||||||
|
strip.putalpha(alpha)
|
||||||
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
strip.save(target)
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def normal_from_height(image: Image.Image, strength: float = 2.0) -> Image.Image:
|
||||||
|
"""Tangent-space normal map from luminance (wraps for tiling)."""
|
||||||
|
gray = image.convert("L")
|
||||||
|
width, height = gray.size
|
||||||
|
px = _pixels(gray)
|
||||||
|
out = bytearray(width * height * 3)
|
||||||
|
for y in range(height):
|
||||||
|
up = ((y - 1) % height) * width
|
||||||
|
down = ((y + 1) % height) * width
|
||||||
|
row = y * width
|
||||||
|
for x in range(width):
|
||||||
|
dx = (px[row + (x + 1) % width] - px[row + (x - 1) % width]) / 255.0
|
||||||
|
dy = (px[down + x] - px[up + x]) / 255.0
|
||||||
|
nx, ny, nz = -dx * strength, -dy * strength, 1.0
|
||||||
|
inv = 1.0 / math.sqrt(nx * nx + ny * ny + 1.0)
|
||||||
|
o = (row + x) * 3
|
||||||
|
out[o] = int((nx * inv * 0.5 + 0.5) * 255)
|
||||||
|
out[o + 1] = int((ny * inv * 0.5 + 0.5) * 255)
|
||||||
|
out[o + 2] = int((nz * inv * 0.5 + 0.5) * 255)
|
||||||
|
return Image.frombytes("RGB", (width, height), bytes(out))
|
||||||
|
|
||||||
|
|
||||||
|
def stripe_period(image: Image.Image, minimum: int = 8) -> int:
|
||||||
|
"""Column period of a striped texture (0 when no clear period is found)."""
|
||||||
|
gray = image.convert("L")
|
||||||
|
width, height = gray.size
|
||||||
|
profile = [sum(_pixels(gray.crop((x, 0, x + 1, height)))) / height
|
||||||
|
for x in range(width)]
|
||||||
|
# Detrend with a moving average: a slow lighting gradient otherwise
|
||||||
|
# correlates strongly at short periods and wins over the real stripes.
|
||||||
|
window = max(8, width // 16)
|
||||||
|
smoothed = [sum(profile[(x + i) % width] for i in range(-window, window + 1))
|
||||||
|
/ (2 * window + 1) for x in range(width)]
|
||||||
|
centred = [profile[x] - smoothed[x] for x in range(width)]
|
||||||
|
|
||||||
|
def correlation(period: int) -> float:
|
||||||
|
return sum(centred[x] * centred[(x + period) % width]
|
||||||
|
for x in range(width)) / width
|
||||||
|
|
||||||
|
scores = {period: correlation(period) for period in range(minimum, width // 2)}
|
||||||
|
if not scores:
|
||||||
|
return 0
|
||||||
|
period = max(scores, key=scores.get)
|
||||||
|
# A real pattern repeats about as strongly as it correlates with itself.
|
||||||
|
return period if scores[period] > 0.35 * correlation(0) else 0
|
||||||
|
|
||||||
|
|
||||||
|
def align_stripes(image: Image.Image) -> Image.Image:
|
||||||
|
"""Resample a striped texture to a whole number of periods so it tiles.
|
||||||
|
|
||||||
|
The edge crossfade in `make_seamless` smears broad stripes. Here the period
|
||||||
|
is measured, then the tile is wrapped and rescaled so exactly `n` periods
|
||||||
|
fit the width: seamless by construction, with a <1 % scale change.
|
||||||
|
"""
|
||||||
|
period = stripe_period(image)
|
||||||
|
if period <= 0:
|
||||||
|
return image
|
||||||
|
width, height = image.size
|
||||||
|
periods = max(1, round(width / period))
|
||||||
|
target_width = int(round(period * periods))
|
||||||
|
columns = max(1, -(-target_width // width)) # ceil
|
||||||
|
strip = Image.new("RGB", (width * columns, height))
|
||||||
|
for index in range(columns):
|
||||||
|
strip.paste(image, (index * width, 0))
|
||||||
|
strip = strip.crop((0, 0, target_width, height))
|
||||||
|
return strip.resize((width, height), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
|
||||||
|
STRIPED = {"awning_canvas"}
|
||||||
|
|
||||||
|
|
||||||
|
def process_texture(source: Path, target: Path, size: int = TILE, name: str = "") -> Path:
|
||||||
|
with Image.open(source) as image:
|
||||||
|
image.load()
|
||||||
|
tile = ImageOps.fit(image.convert("RGB"), (size, size),
|
||||||
|
method=Image.Resampling.LANCZOS)
|
||||||
|
if name in STRIPED:
|
||||||
|
aligned = align_stripes(tile)
|
||||||
|
tile = aligned.resize((size, size), Image.Resampling.LANCZOS)
|
||||||
|
else:
|
||||||
|
tile = asset_pipeline.make_seamless(tile, band=max(16, size // 16))
|
||||||
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
tile.save(target)
|
||||||
|
normal_target = target.with_name(target.stem + "_normal.png")
|
||||||
|
if not normal_target.exists():
|
||||||
|
normal_from_height(tile.filter(ImageFilter.GaussianBlur(1.0))).save(normal_target)
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_process(args) -> int:
|
||||||
|
written = 0
|
||||||
|
for name in _select(args.group, args.only):
|
||||||
|
group = CATALOGUE[name][0]
|
||||||
|
if group == "scene":
|
||||||
|
continue
|
||||||
|
for index, source in enumerate(raw_variants(name), start=1):
|
||||||
|
suffix = "" if index == 1 else f"-v{index}"
|
||||||
|
target = READY_DIR / f"{name}{suffix}.png"
|
||||||
|
if args.new:
|
||||||
|
target = next_free_path(target)
|
||||||
|
elif target.exists():
|
||||||
|
print(f"skip {_rel(target)} (exists)")
|
||||||
|
continue
|
||||||
|
if group == "backdrop":
|
||||||
|
process_backdrop(source, target)
|
||||||
|
else:
|
||||||
|
process_texture(source, target, name=name)
|
||||||
|
written += 1
|
||||||
|
print(f"wrote {_rel(target)}")
|
||||||
|
print(f"processed {written} new candidate(s)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def audit_backdrop(path: Path, seam_limit: float = 24.0) -> dict:
|
||||||
|
"""Backdrop rules: 2:1 size, real transparency, opaque base, tiling edge."""
|
||||||
|
errors = []
|
||||||
|
with Image.open(path) as image:
|
||||||
|
image.load()
|
||||||
|
rgba = image.convert("RGBA")
|
||||||
|
width, height = rgba.size
|
||||||
|
if (width, height) != (BACKDROP_W, BACKDROP_H):
|
||||||
|
errors.append(f"size {width}x{height}, expected {BACKDROP_W}x{BACKDROP_H}")
|
||||||
|
alpha = rgba.getchannel("A")
|
||||||
|
if alpha.getextrema()[0] == 255:
|
||||||
|
errors.append("no transparent sky")
|
||||||
|
if min(_pixels(alpha.crop((0, height - 2, width, height)))) < 255:
|
||||||
|
errors.append("bottom edge has holes")
|
||||||
|
# Compare what the alpha-scissor material shows: transparent pixels carry
|
||||||
|
# arbitrary RGB, so flatten onto grey first.
|
||||||
|
flat = Image.alpha_composite(Image.new("RGBA", rgba.size, (128, 128, 128, 255)), rgba)
|
||||||
|
left = flat.crop((0, 0, 1, height)).convert("RGB")
|
||||||
|
right = flat.crop((width - 1, 0, width, height)).convert("RGB")
|
||||||
|
diffs = [sum(abs(a - b) for a, b in zip(lp, rp)) / 3.0
|
||||||
|
for lp, rp in zip(_pixels(left), _pixels(right))]
|
||||||
|
seam = sum(diffs) / len(diffs)
|
||||||
|
if seam > seam_limit:
|
||||||
|
errors.append(f"horizontal seam {seam:.1f} > {seam_limit}")
|
||||||
|
return {"path": str(path), "size": [width, height], "seam": round(seam, 1),
|
||||||
|
"quality": "bad" if errors else "good", "errors": errors}
|
||||||
|
|
||||||
|
|
||||||
|
def _column_profile(image: Image.Image) -> list:
|
||||||
|
"""Mean RGB of every column, as one flattened row (fast, all edges kept)."""
|
||||||
|
width, height = image.size
|
||||||
|
column = image.convert("RGB").resize((width, 1), Image.Resampling.BOX)
|
||||||
|
return _pixels(column)
|
||||||
|
|
||||||
|
|
||||||
|
def _step(a, b) -> float:
|
||||||
|
return sum(abs(x - y) for x, y in zip(a[:3], b[:3])) / 3.0
|
||||||
|
|
||||||
|
|
||||||
|
def audit_striped_tile(path: Path, seam_limit: float = 18.0) -> dict:
|
||||||
|
"""Tile audit for broad stripes: compare the wrap step with a normal step.
|
||||||
|
|
||||||
|
A hard stripe boundary can sit exactly on the tile edge, which makes the
|
||||||
|
plain edge-difference check fail even though the tile wraps perfectly.
|
||||||
|
"""
|
||||||
|
with Image.open(path) as image:
|
||||||
|
image.load()
|
||||||
|
rgb = image.convert("RGB")
|
||||||
|
width, height = rgb.size
|
||||||
|
profile = _column_profile(rgb)
|
||||||
|
wrap = _step(profile[-1], profile[0])
|
||||||
|
steps = [_step(profile[x], profile[x + 1]) for x in range(width - 1)]
|
||||||
|
typical = sum(steps) / len(steps)
|
||||||
|
# Stripes are hard-edged, so a hard step at the edge is expected. What
|
||||||
|
# matters is that the wrap is no harsher than the harshest step inside the
|
||||||
|
# tile: that means the pattern continues instead of jumping.
|
||||||
|
hardest = max(steps)
|
||||||
|
errors = []
|
||||||
|
if (width, height) != (TILE, TILE):
|
||||||
|
errors.append(f"size {width}x{height}, expected {TILE}x{TILE}")
|
||||||
|
if wrap > 1.25 * hardest + seam_limit * 0.25:
|
||||||
|
errors.append(f"tile edge step {wrap:.1f} exceeds the harshest interior step "
|
||||||
|
f"{hardest:.1f}")
|
||||||
|
return {"path": str(path), "size": [width, height], "quality": "bad" if errors else "good",
|
||||||
|
"errors": errors, "wrap_step": round(wrap, 1), "typical_step": round(typical, 1),
|
||||||
|
"hardest_step": round(hardest, 1), "stripe_period": stripe_period(rgb)}
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_audit(args) -> int:
|
||||||
|
bad = 0
|
||||||
|
for path in sorted(READY_DIR.glob("*.png")):
|
||||||
|
if path.stem.endswith("_normal"):
|
||||||
|
role = "texture"
|
||||||
|
elif path.stem.startswith("skyline"):
|
||||||
|
role = "backdrop"
|
||||||
|
result = audit_backdrop(path)
|
||||||
|
bad += result["quality"] == "bad"
|
||||||
|
print(f"{result['quality'].upper():4} {role:8} {_rel(path)} {result['size']}")
|
||||||
|
for error in result["errors"]:
|
||||||
|
print(" -", error)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
role = "tile"
|
||||||
|
if role == "tile" and path.stem.split("-v")[0] in STRIPED:
|
||||||
|
result = audit_striped_tile(path)
|
||||||
|
else:
|
||||||
|
result = asset_pipeline.audit_image(path, role=role)
|
||||||
|
flag = result["quality"].upper()
|
||||||
|
bad += result["quality"] == "bad"
|
||||||
|
print(f"{flag:4} {role:8} {_rel(path)} {result['size']}")
|
||||||
|
for error in result["errors"]:
|
||||||
|
print(" -", error)
|
||||||
|
return 1 if bad else 0
|
||||||
|
|
||||||
|
|
||||||
|
PICKS_DIR = READY_DIR / "picks"
|
||||||
|
|
||||||
|
|
||||||
|
def promote(name: str, source_name: str) -> Path:
|
||||||
|
"""Copy a validated candidate to the stable tracked path `picks/<name>.png`.
|
||||||
|
|
||||||
|
Never overwrites: an existing pick is left alone (the promotion is a no-op
|
||||||
|
with a warning) and a renamed pick gets the next free `-vN` path.
|
||||||
|
"""
|
||||||
|
source = READY_DIR / f"{source_name}.png"
|
||||||
|
if not source.is_file():
|
||||||
|
raise FileNotFoundError(source)
|
||||||
|
target = PICKS_DIR / f"{name}.png"
|
||||||
|
if target.is_file():
|
||||||
|
print(f"keep {_rel(target)} (already promoted; remove it to re-promote)")
|
||||||
|
return target
|
||||||
|
PICKS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
with Image.open(source) as image:
|
||||||
|
image.load()
|
||||||
|
image.save(target)
|
||||||
|
source_normal = READY_DIR / f"{source_name}_normal.png"
|
||||||
|
target_normal = PICKS_DIR / f"{name}_normal.png"
|
||||||
|
if source_normal.is_file() and not target_normal.exists():
|
||||||
|
with Image.open(source_normal) as image:
|
||||||
|
image.load()
|
||||||
|
image.save(target_normal)
|
||||||
|
print(f"promoted {_rel(source)} -> {_rel(target)}")
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_promote(args) -> int:
|
||||||
|
picks = dict(PICKS)
|
||||||
|
for override in (args.pick or "").split(","):
|
||||||
|
if "=" in override:
|
||||||
|
key, value = override.split("=", 1)
|
||||||
|
picks[key.strip()] = value.strip()
|
||||||
|
if not PICKS_DIR.joinpath("..").exists():
|
||||||
|
pass
|
||||||
|
failures = 0
|
||||||
|
for name, source in picks.items():
|
||||||
|
try:
|
||||||
|
promote(name, source)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"missing candidate for {name}: {source}", file=sys.stderr)
|
||||||
|
failures += 1
|
||||||
|
return 1 if failures else 0
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Realism report
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def image_metrics(path: Path) -> dict:
|
||||||
|
"""Coarse detail/realism signals (Pillow only)."""
|
||||||
|
with Image.open(path) as image:
|
||||||
|
image.load()
|
||||||
|
rgb = image.convert("RGB").resize((640, 360), Image.Resampling.LANCZOS)
|
||||||
|
gray = ImageOps.grayscale(rgb)
|
||||||
|
edges = ImageStat.Stat(gray.filter(ImageFilter.FIND_EDGES)).mean[0]
|
||||||
|
# High-frequency texture: difference between the frame and a blurred copy.
|
||||||
|
blur = gray.filter(ImageFilter.GaussianBlur(2))
|
||||||
|
detail = ImageStat.Stat(Image.eval(
|
||||||
|
Image.blend(gray, ImageOps.invert(blur), 0.5), lambda v: abs(v - 128) * 2)).mean[0]
|
||||||
|
hsv = rgb.convert("HSV")
|
||||||
|
sat = ImageStat.Stat(hsv.getchannel("S"))
|
||||||
|
return {
|
||||||
|
"edge_density": round(edges, 2),
|
||||||
|
"fine_detail": round(detail, 2),
|
||||||
|
"contrast": round(ImageStat.Stat(gray).stddev[0], 2),
|
||||||
|
"saturation_mean": round(sat.mean[0], 2),
|
||||||
|
"colour_spread": round(sum(ImageStat.Stat(rgb).stddev) / 3.0, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def metric_gap(capture: dict, target: dict) -> float:
|
||||||
|
"""Mean relative distance of the capture's metrics from the target (0 = same)."""
|
||||||
|
keys = ("edge_density", "fine_detail", "contrast", "colour_spread")
|
||||||
|
gaps = [abs(capture[k] - target[k]) / max(target[k], 1e-6) for k in keys]
|
||||||
|
return round(sum(gaps) / len(gaps), 3)
|
||||||
|
|
||||||
|
|
||||||
|
JUDGE_INSTRUCTIONS = (
|
||||||
|
"You are an art director for a realistic 3D game. Image 1 is the TARGET look "
|
||||||
|
"(an AI concept frame). Image 2 is an in-game CAPTURE of the same kind of "
|
||||||
|
"scene. Ignore HUD text and exact layout. Score how realistic the CAPTURE "
|
||||||
|
"looks compared with the target, each 0-10: lighting, materials, "
|
||||||
|
"texture_detail, backdrop (sky, skyline and distance haze), characters, overall. "
|
||||||
|
"Then list up to five concrete, actionable fixes ranked by impact. Reply ONLY "
|
||||||
|
"with JSON: {\"scores\": {\"lighting\": n, \"materials\": n, \"texture_detail\": n, "
|
||||||
|
"\"backdrop\": n, \"characters\": n, \"overall\": n}, \"fixes\": [\"...\"]}")
|
||||||
|
|
||||||
|
|
||||||
|
def _data_url(path: Path) -> str:
|
||||||
|
import base64
|
||||||
|
return "data:image/png;base64," + base64.b64encode(Path(path).read_bytes()).decode("ascii")
|
||||||
|
|
||||||
|
|
||||||
|
def build_realism_request(model: str, target: Path, capture: Path) -> dict:
|
||||||
|
return {
|
||||||
|
"model": model,
|
||||||
|
"messages": [{"role": "user", "content": [
|
||||||
|
{"type": "text", "text": JUDGE_INSTRUCTIONS},
|
||||||
|
{"type": "image_url", "image_url": {"url": _data_url(target)}},
|
||||||
|
{"type": "image_url", "image_url": {"url": _data_url(capture)}},
|
||||||
|
]}],
|
||||||
|
"temperature": 0,
|
||||||
|
"response_format": {"type": "json_object"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_realism_response(payload: dict) -> dict:
|
||||||
|
try:
|
||||||
|
text = payload["choices"][0]["message"]["content"]
|
||||||
|
except (KeyError, IndexError, TypeError):
|
||||||
|
return {"scores": {}, "fixes": ["unreadable model response"]}
|
||||||
|
if isinstance(text, list):
|
||||||
|
text = "".join(p.get("text", "") for p in text if isinstance(p, dict))
|
||||||
|
text = str(text)
|
||||||
|
start, end = text.find("{"), text.rfind("}")
|
||||||
|
try:
|
||||||
|
data = json.loads(text[start:end + 1]) if start >= 0 else {}
|
||||||
|
except ValueError:
|
||||||
|
return {"scores": {}, "fixes": [f"non-JSON response: {text[:120]}"]}
|
||||||
|
scores = {}
|
||||||
|
for key, value in (data.get("scores") or {}).items():
|
||||||
|
try:
|
||||||
|
scores[str(key)] = max(0.0, min(10.0, float(value)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
fixes = data.get("fixes") or []
|
||||||
|
if not isinstance(fixes, list):
|
||||||
|
fixes = [str(fixes)]
|
||||||
|
return {"scores": scores, "fixes": [str(f) for f in fixes][:5]}
|
||||||
|
|
||||||
|
|
||||||
|
def _call_openrouter(body: dict, timeout: int = 180) -> dict:
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
key = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||||
|
if not key:
|
||||||
|
raise ValueError("OPENROUTER_API_KEY is not set")
|
||||||
|
req = urllib.request.Request(
|
||||||
|
OPENROUTER_CHAT_URL, data=json.dumps(body).encode("utf-8"),
|
||||||
|
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json",
|
||||||
|
"HTTP-Referer": "https://git.allucanget.biz/zwitschi/security-game",
|
||||||
|
"X-Title": "Gig Line"}, method="POST")
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||||
|
return json.loads(resp.read().decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
raise ValueError(f"OpenRouter HTTP {exc.code}: {exc.read().decode('utf-8', 'replace')[:300]}")
|
||||||
|
|
||||||
|
|
||||||
|
def judge_capture(model: str, target: Path, capture: Path, attempts: int = 2,
|
||||||
|
repeats: int = 1) -> dict:
|
||||||
|
"""Vision-judge one capture; retry transient errors, average repeat scores.
|
||||||
|
|
||||||
|
A single score from a small vision model swings by ±1-2, so `repeats` runs
|
||||||
|
are averaged and the spread is kept (`score_range`) for the report.
|
||||||
|
"""
|
||||||
|
runs = []
|
||||||
|
error = None
|
||||||
|
for _ in range(max(1, repeats)):
|
||||||
|
for _attempt in range(attempts):
|
||||||
|
try:
|
||||||
|
runs.append(parse_realism_response(_call_openrouter(
|
||||||
|
build_realism_request(model, target, capture))))
|
||||||
|
break
|
||||||
|
except (ValueError, OSError) as exc:
|
||||||
|
error = exc
|
||||||
|
if not runs:
|
||||||
|
return {"scores": {}, "fixes": [f"judge failed: {error}"]}
|
||||||
|
keys = {key for run in runs for key in run["scores"]}
|
||||||
|
scores = {}
|
||||||
|
spread = {}
|
||||||
|
for key in keys:
|
||||||
|
values = [run["scores"][key] for run in runs if key in run["scores"]]
|
||||||
|
scores[key] = round(sum(values) / len(values), 2)
|
||||||
|
spread[key] = [min(values), max(values)]
|
||||||
|
fixes = []
|
||||||
|
for run in runs:
|
||||||
|
for fix in run["fixes"]:
|
||||||
|
if fix not in fixes:
|
||||||
|
fixes.append(fix)
|
||||||
|
return {"scores": scores, "score_range": spread, "runs": len(runs),
|
||||||
|
"fixes": fixes[:5]}
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_realism(args) -> int:
|
||||||
|
run = Path(args.run)
|
||||||
|
run = run if run.is_absolute() else ROOT / run
|
||||||
|
sets = [d for d in sorted(run.iterdir()) if d.is_dir()] if run.is_dir() else []
|
||||||
|
if not sets:
|
||||||
|
print(f"no capture sets under {_rel(run)}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
if args.judge:
|
||||||
|
_load_env_file()
|
||||||
|
report = {"run": _rel(run), "judge_model": args.model if args.judge else None,
|
||||||
|
"sets": {}}
|
||||||
|
for capture_set in sets:
|
||||||
|
rows = {}
|
||||||
|
for capture in sorted(capture_set.glob("*.png")):
|
||||||
|
concept_name = SHOTS.get(capture.stem)
|
||||||
|
target = CONCEPT_DIR / f"{concept_name}.png" if concept_name else None
|
||||||
|
if target is None or not target.is_file():
|
||||||
|
continue
|
||||||
|
cap_m, tgt_m = image_metrics(capture), image_metrics(target)
|
||||||
|
row = {"target": _rel(target), "metrics": cap_m, "target_metrics": tgt_m,
|
||||||
|
"metric_gap": metric_gap(cap_m, tgt_m)}
|
||||||
|
if args.judge:
|
||||||
|
row["judge"] = judge_capture(args.model, target, capture,
|
||||||
|
repeats=args.repeats)
|
||||||
|
rows[capture.stem] = row
|
||||||
|
report["sets"][capture_set.name] = rows
|
||||||
|
json_path = next_free_path(run / "report.json")
|
||||||
|
json_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
||||||
|
md_path = next_free_path(run / "report.md")
|
||||||
|
md_path.write_text(render_markdown(report), encoding="utf-8")
|
||||||
|
sheet = write_sheet(run, sets)
|
||||||
|
print(render_markdown(report))
|
||||||
|
print(f"wrote {_rel(json_path)}, {_rel(md_path)}" + (f" and {_rel(sheet)}" if sheet else ""))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def write_sheet(run: Path, sets) -> Path:
|
||||||
|
"""Committed JPEG contact sheet: one row per shot, target + one column per set."""
|
||||||
|
from PIL import ImageDraw
|
||||||
|
shots = sorted({p.stem for d in sets for p in d.glob("*.png") if p.stem in SHOTS})
|
||||||
|
if not shots:
|
||||||
|
return None
|
||||||
|
cw, ch = 480, 270
|
||||||
|
cols = 1 + len(sets)
|
||||||
|
sheet = Image.new("RGB", (cw * cols, (ch + 20) * len(shots) + 20), (22, 24, 28))
|
||||||
|
draw = ImageDraw.Draw(sheet)
|
||||||
|
for c, label in enumerate(["target"] + [d.name for d in sets]):
|
||||||
|
draw.text((c * cw + 6, 4), label, fill=(235, 235, 235))
|
||||||
|
for r, shot in enumerate(shots):
|
||||||
|
y = 20 + r * (ch + 20)
|
||||||
|
tiles = [CONCEPT_DIR / f"{SHOTS[shot]}.png"] + [d / f"{shot}.png" for d in sets]
|
||||||
|
for c, path in enumerate(tiles):
|
||||||
|
if path.is_file():
|
||||||
|
with Image.open(path) as image:
|
||||||
|
sheet.paste(image.convert("RGB").resize((cw, ch), Image.Resampling.LANCZOS),
|
||||||
|
(c * cw, y))
|
||||||
|
draw.text((6, y + ch + 3), f"{shot} (target: {SHOTS[shot]})", fill=(200, 200, 200))
|
||||||
|
out = next_free_path(run / "sheet.jpg")
|
||||||
|
sheet.save(out, quality=85, optimize=True)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
SCORE_KEYS = ("overall", "lighting", "materials", "texture_detail", "backdrop", "characters")
|
||||||
|
|
||||||
|
|
||||||
|
def summarize(report: dict) -> dict:
|
||||||
|
"""Mean metric gap and judge scores per capture set.
|
||||||
|
|
||||||
|
Also reports the mean over shots every set has (`*_common`), because a set
|
||||||
|
that captured fewer shots otherwise compares different material.
|
||||||
|
"""
|
||||||
|
sets = {name: rows for name, rows in report.get("sets", {}).items() if rows}
|
||||||
|
common = set.intersection(*[set(rows) for rows in sets.values()]) if sets else set()
|
||||||
|
out = {}
|
||||||
|
for set_name, rows in sets.items():
|
||||||
|
subset = {k: v for k, v in rows.items() if k in common}
|
||||||
|
|
||||||
|
def mean_gap(items):
|
||||||
|
return round(sum(r["metric_gap"] for r in items.values()) / max(1, len(items)), 3)
|
||||||
|
|
||||||
|
def mean_scores(items):
|
||||||
|
means = {}
|
||||||
|
for key in SCORE_KEYS:
|
||||||
|
values = [r["judge"]["scores"][key] for r in items.values()
|
||||||
|
if key in r.get("judge", {}).get("scores", {})]
|
||||||
|
if values:
|
||||||
|
means[key] = round(sum(values) / len(values), 2)
|
||||||
|
return means
|
||||||
|
|
||||||
|
row = {"metric_gap": mean_gap(rows)}
|
||||||
|
row.update(mean_scores(rows))
|
||||||
|
row["shots"] = len(rows)
|
||||||
|
if subset and len(subset) != len(rows):
|
||||||
|
row["metric_gap_common"] = mean_gap(subset)
|
||||||
|
for key, value in mean_scores(subset).items():
|
||||||
|
row[key + "_common"] = value
|
||||||
|
row["common_shots"] = len(subset)
|
||||||
|
out[set_name] = row
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(report: dict) -> str:
|
||||||
|
lines = [f"# Realism report: `{report['run']}`", ""]
|
||||||
|
if report.get("judge_model"):
|
||||||
|
lines.append(f"Vision judge: `{report['judge_model']}` (scores 0-10, higher is "
|
||||||
|
"more realistic compared with the concept target).")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("| set | shot | target | gap ↓ | edges | detail | contrast | overall | "
|
||||||
|
"lighting | materials | texture | backdrop |")
|
||||||
|
lines.append("| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |")
|
||||||
|
for set_name, rows in report["sets"].items():
|
||||||
|
for shot, row in rows.items():
|
||||||
|
s = row.get("judge", {}).get("scores", {})
|
||||||
|
m = row["metrics"]
|
||||||
|
lines.append(
|
||||||
|
f"| {set_name} | {shot} | {Path(row['target']).stem} | {row['metric_gap']} | "
|
||||||
|
f"{m['edge_density']} | {m['fine_detail']} | {m['contrast']} | "
|
||||||
|
f"{s.get('overall', '-')} | {s.get('lighting', '-')} | "
|
||||||
|
f"{s.get('materials', '-')} | {s.get('texture_detail', '-')} | "
|
||||||
|
f"{s.get('backdrop', '-')} |")
|
||||||
|
summary = summarize(report)
|
||||||
|
if summary:
|
||||||
|
lines += ["", "Mean scores per set (all captured shots):", "",
|
||||||
|
"| set | shots | gap ↓ | " + " | ".join(SCORE_KEYS) + " |",
|
||||||
|
"| --- | --- | --- | " + " | ".join("---" for _ in SCORE_KEYS) + " |"]
|
||||||
|
for set_name, row in summary.items():
|
||||||
|
lines.append(f"| {set_name} | {row['shots']} | {row['metric_gap']} | " +
|
||||||
|
" | ".join(str(row.get(k, "-")) for k in SCORE_KEYS) + " |")
|
||||||
|
shared = [row.get("common_shots") for row in summary.values()]
|
||||||
|
if any(count for count in shared):
|
||||||
|
lines += ["", "Mean over the shots every set captured (like for like):", "",
|
||||||
|
"| set | shots | gap ↓ | " + " | ".join(SCORE_KEYS) + " |",
|
||||||
|
"| --- | --- | --- | " + " | ".join("---" for _ in SCORE_KEYS) + " |"]
|
||||||
|
for set_name, row in summary.items():
|
||||||
|
if "common_shots" not in row:
|
||||||
|
continue
|
||||||
|
lines.append(
|
||||||
|
f"| {set_name} | {row['common_shots']} | {row['metric_gap_common']} | " +
|
||||||
|
" | ".join(str(row.get(k + "_common", "-")) for k in SCORE_KEYS) + " |")
|
||||||
|
for set_name, rows in report["sets"].items():
|
||||||
|
for shot, row in rows.items():
|
||||||
|
fixes = row.get("judge", {}).get("fixes", [])
|
||||||
|
if fixes:
|
||||||
|
lines += ["", f"**{set_name} / {shot}** fixes:"]
|
||||||
|
lines += [f"- {fix}" for fix in fixes]
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def cmd_list(_args) -> int:
|
||||||
|
for name, (group, _) in CATALOGUE.items():
|
||||||
|
have = len(raw_variants(name))
|
||||||
|
print(f"{group:8} {name:32} raw={have}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||||
|
sub.add_parser("list").set_defaults(handler=cmd_list)
|
||||||
|
gen = sub.add_parser("generate")
|
||||||
|
gen.add_argument("--group", choices=("scene", "backdrop", "texture"))
|
||||||
|
gen.add_argument("--only", default="")
|
||||||
|
gen.add_argument("--variants", type=int, default=1)
|
||||||
|
gen.add_argument("--model", default=DEFAULT_MODEL)
|
||||||
|
gen.add_argument("--request-size", type=int, default=1536)
|
||||||
|
gen.set_defaults(handler=cmd_generate)
|
||||||
|
proc = sub.add_parser("process")
|
||||||
|
proc.add_argument("--group", choices=("backdrop", "texture"))
|
||||||
|
proc.add_argument("--only", default="")
|
||||||
|
proc.add_argument("--new", action="store_true",
|
||||||
|
help="write new variants even when candidates exist")
|
||||||
|
proc.set_defaults(handler=cmd_process)
|
||||||
|
prom = sub.add_parser("promote")
|
||||||
|
prom.add_argument("--pick", default="", help="name=candidate overrides")
|
||||||
|
prom.set_defaults(handler=cmd_promote)
|
||||||
|
sub.add_parser("audit").set_defaults(handler=cmd_audit)
|
||||||
|
real = sub.add_parser("realism")
|
||||||
|
real.add_argument("--run", required=True)
|
||||||
|
real.add_argument("--judge", action="store_true")
|
||||||
|
real.add_argument("--model", default=DEFAULT_JUDGE_MODEL)
|
||||||
|
real.add_argument("--repeats", type=int, default=1,
|
||||||
|
help="average N judge runs per shot (scores are noisy)")
|
||||||
|
real.set_defaults(handler=cmd_realism)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None) -> int:
|
||||||
|
args = build_parser().parse_args(argv)
|
||||||
|
return args.handler(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -991,7 +991,13 @@ def judge(args):
|
|||||||
"differences": [f"dry run: {len(body['messages'][0]['content'])} parts"],
|
"differences": [f"dry run: {len(body['messages'][0]['content'])} parts"],
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
verdict = parse_judge_response(_call_openrouter(body))
|
# Providers occasionally answer a valid key with a transient HTTP 400;
|
||||||
|
# one retry keeps a whole judge run from dying on a single hiccup.
|
||||||
|
try:
|
||||||
|
verdict = parse_judge_response(_call_openrouter(body))
|
||||||
|
except ValueError as exc:
|
||||||
|
print(f"retrying {capture.name}: {exc}", file=sys.stderr)
|
||||||
|
verdict = parse_judge_response(_call_openrouter(body))
|
||||||
verdict.update({"capture": capture.name, "screen": design_name,
|
verdict.update({"capture": capture.name, "screen": design_name,
|
||||||
"class": classify(verdict, args.fail_confidence)})
|
"class": classify(verdict, args.fail_confidence)})
|
||||||
results.append(verdict)
|
results.append(verdict)
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
import scene_concepts as sc
|
||||||
|
|
||||||
|
|
||||||
|
class CatalogueTests(unittest.TestCase):
|
||||||
|
def test_groups_and_prompts(self):
|
||||||
|
groups = {g for g, _ in sc.CATALOGUE.values()}
|
||||||
|
self.assertEqual(groups, {"scene", "backdrop", "texture"})
|
||||||
|
for name in sc.CATALOGUE:
|
||||||
|
prompt = sc.compose_prompt(name)
|
||||||
|
self.assertGreater(len(prompt), 80, name)
|
||||||
|
for day in ("skyline_day", "skyline_dusk", "skyline_night"):
|
||||||
|
self.assertEqual(sc.CATALOGUE[day][0], "backdrop")
|
||||||
|
|
||||||
|
def test_shots_point_at_scene_concepts(self):
|
||||||
|
for shot, concept in sc.SHOTS.items():
|
||||||
|
self.assertEqual(sc.CATALOGUE[concept][0], "scene", shot)
|
||||||
|
|
||||||
|
def test_paths_stay_in_their_folders(self):
|
||||||
|
self.assertTrue(sc.raw_path("boulevard_skyline_day").is_relative_to(sc.CONCEPT_DIR))
|
||||||
|
self.assertTrue(sc.raw_path("skyline_day").is_relative_to(sc.RAW_DIR))
|
||||||
|
self.assertTrue(sc.raw_path("asphalt").is_relative_to(sc.RAW_DIR))
|
||||||
|
|
||||||
|
|
||||||
|
class NoOverwriteTests(unittest.TestCase):
|
||||||
|
def test_next_free_path_never_returns_an_existing_file(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
base = Path(tmp) / "a.png"
|
||||||
|
self.assertEqual(sc.next_free_path(base), base)
|
||||||
|
base.write_bytes(b"x")
|
||||||
|
second = sc.next_free_path(base)
|
||||||
|
self.assertEqual(second.name, "a-v2.png")
|
||||||
|
second.write_bytes(b"x")
|
||||||
|
self.assertEqual(sc.next_free_path(base).name, "a-v3.png")
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessingTests(unittest.TestCase):
|
||||||
|
def _skyline(self, opaque=False):
|
||||||
|
image = Image.new("RGBA", (300, 200), (120, 170, 230, 255 if opaque else 0))
|
||||||
|
for x in range(300):
|
||||||
|
top = 80 + (x * 7) % 50
|
||||||
|
for y in range(top, 200):
|
||||||
|
image.putpixel((x, y), (40 + x % 30, 40, 50, 255))
|
||||||
|
return image
|
||||||
|
|
||||||
|
def test_key_sky_makes_an_opaque_sky_transparent(self):
|
||||||
|
keyed = sc.key_sky(self._skyline(opaque=True))
|
||||||
|
self.assertEqual(keyed.getpixel((5, 5))[3], 0)
|
||||||
|
self.assertEqual(keyed.getpixel((5, 199))[3], 255)
|
||||||
|
|
||||||
|
def test_band_keeps_tower_tops(self):
|
||||||
|
band = sc._band(self._skyline())
|
||||||
|
self.assertAlmostEqual(band.size[0] / band.size[1], 2.0, delta=0.6)
|
||||||
|
alpha = band.getchannel("A")
|
||||||
|
self.assertEqual(alpha.getpixel((0, band.size[1] - 1)), 255)
|
||||||
|
|
||||||
|
def test_processed_backdrop_audits_clean(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
src = Path(tmp) / "raw.png"
|
||||||
|
self._skyline().save(src)
|
||||||
|
out = sc.process_backdrop(src, Path(tmp) / "skyline.png")
|
||||||
|
result = sc.audit_backdrop(out)
|
||||||
|
self.assertEqual(result["errors"], [])
|
||||||
|
|
||||||
|
def test_texture_processing_writes_tile_and_normal(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
src = Path(tmp) / "raw.png"
|
||||||
|
Image.effect_noise((128, 128), 40).convert("RGB").save(src)
|
||||||
|
out = sc.process_texture(src, Path(tmp) / "tile.png", size=64)
|
||||||
|
self.assertEqual(Image.open(out).size, (64, 64))
|
||||||
|
normal = Image.open(out.with_name("tile_normal.png"))
|
||||||
|
self.assertEqual(normal.size, (64, 64))
|
||||||
|
r, g, b = normal.getpixel((10, 10))
|
||||||
|
self.assertGreater(b, 128) # normals point out of the surface
|
||||||
|
|
||||||
|
|
||||||
|
def _striped(period=64, size=256):
|
||||||
|
image = Image.new("RGB", (size, size), (200, 190, 170))
|
||||||
|
for x in range(size):
|
||||||
|
if (x // (period // 2)) % 2 == 0:
|
||||||
|
for y in range(size):
|
||||||
|
image.putpixel((x, y), (150, 40, 45))
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
class StripeTests(unittest.TestCase):
|
||||||
|
def test_period_found_and_tile_wraps(self):
|
||||||
|
tile = _striped()
|
||||||
|
self.assertEqual(sc.stripe_period(tile), 64)
|
||||||
|
aligned = sc.align_stripes(tile)
|
||||||
|
self.assertEqual(aligned.size, tile.size)
|
||||||
|
# The wrap step along x matches a normal neighbouring-column step.
|
||||||
|
profile = sc._column_profile(aligned)
|
||||||
|
wrap = sc._step(profile[-1], profile[0])
|
||||||
|
hardest = max(sc._step(profile[x], profile[x + 1]) for x in range(aligned.size[0] - 1))
|
||||||
|
self.assertLessEqual(wrap, hardest * 1.25 + 0.5)
|
||||||
|
|
||||||
|
def test_flat_texture_has_no_period(self):
|
||||||
|
self.assertEqual(sc.stripe_period(Image.new("RGB", (128, 128), (90, 90, 90))), 0)
|
||||||
|
flat = Image.new("RGB", (64, 64), (128, 128, 128))
|
||||||
|
self.assertEqual(sc.align_stripes(flat).size, flat.size)
|
||||||
|
|
||||||
|
def test_striped_audit_accepts_a_boundary_on_the_edge(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
path = Path(tmp) / "awning_canvas.png"
|
||||||
|
image = _striped(period=64, size=sc.TILE)
|
||||||
|
image.save(path)
|
||||||
|
result = sc.audit_striped_tile(path)
|
||||||
|
self.assertEqual(result["errors"], [])
|
||||||
|
|
||||||
|
|
||||||
|
class PromoteTests(unittest.TestCase):
|
||||||
|
def test_promote_copies_and_never_overwrites(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
picks = Path(tmp) / "picks"
|
||||||
|
global_dir = sc.PICKS_DIR
|
||||||
|
sc.PICKS_DIR = picks
|
||||||
|
try:
|
||||||
|
sc.READY_DIR = Path(tmp)
|
||||||
|
Image.new("RGB", (16, 16), (10, 20, 30)).save(Path(tmp) / "src.png")
|
||||||
|
Image.new("RGB", (16, 16), (128, 128, 255)).save(Path(tmp) / "src_normal.png")
|
||||||
|
target = sc.promote("pick", "src")
|
||||||
|
self.assertTrue(target.is_file())
|
||||||
|
self.assertTrue((picks / "pick_normal.png").is_file())
|
||||||
|
Image.new("RGB", (16, 16), (0, 0, 0)).save(Path(tmp) / "src2.png")
|
||||||
|
again = sc.promote("pick", "src2")
|
||||||
|
self.assertEqual(again, target)
|
||||||
|
self.assertNotEqual(Image.open(target).getpixel((0, 0)), (0, 0, 0))
|
||||||
|
finally:
|
||||||
|
sc.PICKS_DIR = global_dir
|
||||||
|
sc.READY_DIR = Path(tmp).parent # restored by the loader below
|
||||||
|
|
||||||
|
def test_picks_exist_in_catalogue(self):
|
||||||
|
for name, source in sc.PICKS.items():
|
||||||
|
self.assertTrue(source.startswith(name), f"{name} <- {source}")
|
||||||
|
|
||||||
|
|
||||||
|
class RealismTests(unittest.TestCase):
|
||||||
|
def test_metrics_and_gap(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
flat = Path(tmp) / "flat.png"
|
||||||
|
busy = Path(tmp) / "busy.png"
|
||||||
|
Image.new("RGB", (320, 180), (128, 128, 128)).save(flat)
|
||||||
|
Image.effect_noise((320, 180), 60).convert("RGB").save(busy)
|
||||||
|
m_flat, m_busy = sc.image_metrics(flat), sc.image_metrics(busy)
|
||||||
|
self.assertLess(m_flat["edge_density"], m_busy["edge_density"])
|
||||||
|
self.assertEqual(sc.metric_gap(m_busy, m_busy), 0.0)
|
||||||
|
|
||||||
|
def test_parse_realism_response_clamps_and_limits(self):
|
||||||
|
payload = {"choices": [{"message": {"content":
|
||||||
|
'{"scores": {"overall": 14, "lighting": "3"}, "fixes": ["a","b","c","d","e","f"]}'}}]}
|
||||||
|
verdict = sc.parse_realism_response(payload)
|
||||||
|
self.assertEqual(verdict["scores"], {"overall": 10.0, "lighting": 3.0})
|
||||||
|
self.assertEqual(len(verdict["fixes"]), 5)
|
||||||
|
self.assertEqual(sc.parse_realism_response({})["scores"], {})
|
||||||
|
|
||||||
|
def test_summary_averages_per_set(self):
|
||||||
|
report = {"sets": {"a": {
|
||||||
|
"x": {"metric_gap": 0.2, "judge": {"scores": {"overall": 2.0}}},
|
||||||
|
"y": {"metric_gap": 0.4, "judge": {"scores": {"overall": 4.0}}}}}}
|
||||||
|
summary = sc.summarize(report)
|
||||||
|
self.assertEqual(summary["a"]["overall"], 3.0)
|
||||||
|
self.assertAlmostEqual(summary["a"]["metric_gap"], 0.3)
|
||||||
|
self.assertIn("Mean scores", sc.render_markdown(
|
||||||
|
{"run": "r", "judge_model": None, "sets": report["sets"] | {}} | {
|
||||||
|
"sets": {"a": {k: dict(v, target="t.png", metrics={
|
||||||
|
"edge_density": 1, "fine_detail": 1, "contrast": 1})
|
||||||
|
for k, v in report["sets"]["a"].items()}}}))
|
||||||
|
|
||||||
|
def test_request_carries_both_images(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
a = Path(tmp) / "a.png"
|
||||||
|
Image.new("RGB", (8, 8)).save(a)
|
||||||
|
body = sc.build_realism_request("m", a, a)
|
||||||
|
parts = body["messages"][0]["content"]
|
||||||
|
self.assertEqual([p["type"] for p in parts], ["text", "image_url", "image_url"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||