Refine environment surfaces and skyline haze
This commit is contained in:
1 parent
d7220c1a0a
commit
fb3da52438
8 files changed
+91
-22
No files matched your search
@@ -13,6 +13,9 @@ Semantic Versioning.
|
||||
- NPC fabric maps now repeat four times across the body and skip the baked
|
||||
per-character AO, removing the large blotchy patches from shirts, jackets and
|
||||
trousers at portrait distance.
|
||||
- The Walk of Fame band now uses the paver/terrazzo texture instead of a flat
|
||||
dark strip, the neighbour awning keeps its cream stripe at close range, and the
|
||||
distant skyline fades into horizon haze instead of reading as a crisp cut-out.
|
||||
|
||||
## [0.9.4] — 2026-11-02
|
||||
|
||||
|
||||
@@ -71,6 +71,13 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
||||
albedo/normal/ORM maps instead of 128² (AI shirt/trousers are resized to 256). The base and all
|
||||
eight library GLBs plus AO maps were rebuilt; texture files total ~0.5 MB, eight GLBs ~5.75 MB,
|
||||
and the 24-NPC smoke remains 6.90 ms.
|
||||
- **Environment surface audit (0.9.5, 2026-11-02):** `capture_realism.gd` gained
|
||||
`surfaces` (Hollywood pavers/asphalt/limestone/facade) and `surfaces_joke`
|
||||
(neighbour awning, canopy, pavers) stages with the HUD hidden. The Walk of Fame
|
||||
band now uses `T_CONCRETE` as dark terrazzo; the neighbour awning uses a neutral
|
||||
tint (`0.62`) and 4× UV so the red/cream stripes survive; the skyline shader
|
||||
fades its lower 42 m toward a per-phase haze colour. Brick/asphalt picks needed
|
||||
no replacement. No seams or broken UV scales found.
|
||||
- **NPC fabric UV/AO (0.9.5, 2026-11-02):** cloth/pants use
|
||||
`Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`, and `NpcAvatar` skips the baked per-character
|
||||
AO on those two slots. The 256² AO was magnifying into large blotches; the fabric now reads as a
|
||||
|
||||
@@ -57,16 +57,22 @@ Compatibility/Web parity, deterministic assets and offline CI.
|
||||
|
||||
### Environment textures and materials
|
||||
|
||||
- [ ] Audit the v2 picks (brick, sidewalk pavers, dry/wet asphalt, awning canvas,
|
||||
- [x] Audit the v2 picks (brick, sidewalk pavers, dry/wet asphalt, awning canvas,
|
||||
day/dusk/night skylines) at close range and in the entrance/street shots for
|
||||
seams, tiling, scale, contrast and normal-map strength.
|
||||
- [ ] Refine brick mortar/wear, paver joints/stains, asphalt aggregate/wet response
|
||||
seams, tiling, scale, contrast and normal-map strength. (New `surfaces` and
|
||||
`surfaces_joke` capture stages; no seam or UV-scale failures found.)
|
||||
- [x] Refine brick mortar/wear, paver joints/stains, asphalt aggregate/wet response
|
||||
and awning weave using the existing image/asset tooling; never overwrite a
|
||||
pick, use `-vN` candidates and promote validated results.
|
||||
- [ ] Check UV scales and material roughness/metallic profiles; keep day/night
|
||||
phase behavior and the wet-street transition intact.
|
||||
- [ ] Review distant skyline haze and foliage/planter surfaces against the concept
|
||||
references; fix obvious flatness without regressing readability.
|
||||
pick, use `-vN` candidates and promote validated results. (The Walk of Fame
|
||||
band now uses the paver/terrazzo texture; the neighbour awning keeps its
|
||||
cream stripe via a neutral tint and 4× UV. Brick/asphalt picks held up.)
|
||||
- [x] Check UV scales and material roughness/metallic profiles; keep day/night
|
||||
phase behavior and the wet-street transition intact. (Profiles and UV
|
||||
scales passed; dry/wet asphalt and phase switching unchanged.)
|
||||
- [x] Review distant skyline haze and foliage/planter surfaces against the concept
|
||||
references; fix obvious flatness without regressing readability. (Added
|
||||
world-height haze fade to the skyline shader; foliage/planters stayed
|
||||
stylized and readable, so no mesh change.)
|
||||
|
||||
### Character and prop textures
|
||||
|
||||
@@ -74,7 +80,8 @@ Compatibility/Web parity, deterministic assets and offline CI.
|
||||
refine fabric weave, skin variation and fixed detail materials. (256² maps rebuilt; fabric
|
||||
now uses a 4× UV weave and skips baked per-character AO, removing the portrait blotches;
|
||||
skin/hair/gear keep AO. Comparison `sheet_npc_baseline_compare-texuv.jpg`.)
|
||||
- [ ] Re-bake AO where geometry changes; verify the 256² maps and material wiring.
|
||||
- [x] Re-bake AO where geometry changes; verify the 256² maps and material wiring.
|
||||
(No character geometry changed in the texture pass; asset/base audits pass.)
|
||||
- [ ] Audit library props and UI/card/icon textures at their actual screen sizes;
|
||||
remove blurry/oversized or unused textures where safe.
|
||||
- [ ] Run texture audits, import, and the full asset test suite; record texture memory
|
||||
|
||||
+10
-2
@@ -235,11 +235,19 @@ godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||||
python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
|
||||
```
|
||||
|
||||
`capture_realism.gd` runs seven stages (day, portrait, evening, late, Joke Shop day, Joke Shop night, studio);
|
||||
`capture_realism.gd` runs nine stages (day, Hollywood surface close-ups, Joke Shop surface close-ups, portrait, evening, late, Joke Shop day, Joke Shop night, studio);
|
||||
`--stages=day,late` limits it while tuning; `--flashlight` turns on the guard lamp for
|
||||
night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one NPC on open sidewalk
|
||||
and frames the face, which is the only capture that can judge avatar detail
|
||||
(`--portrait-type=N` picks a role). The generated realism runs under
|
||||
(`--portrait-type=N` picks a role). For the 0.9.5 texture audit, run the surface
|
||||
stages directly; the HUD is hidden for them:
|
||||
|
||||
```bash
|
||||
godot --path . --script res://src/test/capture_realism.gd \
|
||||
-- --out=res://tmp/surfaces --stages=surfaces,surfaces_joke
|
||||
```
|
||||
|
||||
The generated realism runs under
|
||||
`docs/screens/realism/` are **local, gitignored QA artifacts**; commit only the
|
||||
resulting decisions/docs, not the run folders. The texture-adoption run compared
|
||||
the pre-adoption captures with the current build: on the shots both sets captured,
|
||||
|
||||
@@ -43,6 +43,7 @@ Implemented so far:
|
||||
- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
|
||||
- NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`.
|
||||
- Release v0.9.4 (2026-11-02): the tagged commit `a57b86c` passed the Gitea checks and full build and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4>.
|
||||
- Environment texture pass (0.9.5 work): `capture_realism.gd` now has hidden-HUD `surfaces`/`surfaces_joke` close-up stages. The Walk of Fame band uses the paver texture as dark terrazzo, the neighbour awning keeps its cream stripe, and the skyline shader blends its lower 42 m toward a per-phase haze colour. Brick, asphalt, limestone and paver UV scales/profiles passed without replacement.
|
||||
- NPC texture pass (0.9.5 work): the NPC base and all eight library characters now use 256² albedo/normal/ORM maps instead of 128². Cloth and pants use `NPC_FABRIC_UV_SCALE` (4× weave) and skip the baked per-character AO, removing the large blotchy patches in portraits; skin, hair and gear keep baked AO. The base and eight GLBs plus AO maps were rebuilt; the 24-NPC smoke remains green.
|
||||
- NPC visual optimization (0.9.4, complete 2026-11-02): all eight variants passed the portrait/readability, proportion, gear, foot/shoe, headgear and final-context gates. The library uses an 8,000-triangle / 8,000-glTF-vertex cap (up from 6,000/5,000; `npc_base` fallback remains 6,000/4,000); current characters measure 4,482–5,474 triangles and 4,260–5,494 vertices. The 48-frame baseline and 96-frame final-context review pass, the hand/gear offset and shoes are in, and the accepted stylized limits are documented in `npc-visual-review.md`.
|
||||
- NPC face-detail pass (0.9.4 work): ala wings and a smaller nose bridge/tip, one rounded mouth slot, and a darker ear concha; all eight rebuilt. The comparison is `sheet_npc_baseline_compare-face-detail.jpg`.
|
||||
|
||||
@@ -17,10 +17,14 @@ 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)},
|
||||
Rules.Phase.EARLY: {"texture": T_SKYLINE_DAY, "tint": Color(0.98, 1.0, 1.04),
|
||||
"haze": Color(0.62, 0.71, 0.82), "haze_strength": 0.36},
|
||||
Rules.Phase.MIDDAY: {"texture": T_SKYLINE_DAY, "tint": Color(1.02, 1.04, 1.08),
|
||||
"haze": Color(0.66, 0.75, 0.85), "haze_strength": 0.34},
|
||||
Rules.Phase.EVENING: {"texture": T_SKYLINE_DUSK, "tint": Color(0.9, 0.92, 1.0),
|
||||
"haze": Color(0.55, 0.43, 0.40), "haze_strength": 0.30},
|
||||
Rules.Phase.LATE: {"texture": T_SKYLINE_NIGHT, "tint": Color(1.5, 1.55, 1.65),
|
||||
"haze": Color(0.07, 0.09, 0.14), "haze_strength": 0.22},
|
||||
}
|
||||
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
|
||||
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street
|
||||
@@ -648,8 +652,10 @@ func _build_sunset_neighbours() -> void:
|
||||
# Ground-floor shopfront and a red canvas awning.
|
||||
_add_box(west, Vector3(8.6, 2.4, 0.12), Vector3(-22.6, 1.5, west_front + 0.08),
|
||||
Color(0.08, 0.10, 0.12), false, null, Vector2.ONE, "glass")
|
||||
# Keep the cream stripe: tinting the whole atlas dark red collapsed the
|
||||
# canvas into a flat band at portrait distance.
|
||||
_add_box(west, Vector3(9.2, 0.22, 1.3), Vector3(-22.6, 3.0, west_front + 0.65),
|
||||
Color(0.48, 0.10, 0.08), true, T_AWNING, Vector2(3, 0.5), "awning")
|
||||
Color(0.62, 0.62, 0.62), true, T_AWNING, Vector2(4, 1), "awning")
|
||||
# Balcony rails on the upper floor.
|
||||
for x in [-24.8, -21.2, -17.6]:
|
||||
_add_box(west, Vector3(2.4, 0.08, 0.85), Vector3(x, 5.6, west_front + 0.55),
|
||||
@@ -1379,8 +1385,10 @@ func _build_walk_of_fame() -> void:
|
||||
var walk := Node3D.new()
|
||||
walk.name = "WalkOfFame"
|
||||
root.add_child(walk)
|
||||
# Reuse the paver map as a dark terrazzo: a flat colour read as a black
|
||||
# strip at close range, while the texture supplies the small aggregate.
|
||||
_add_box(walk, Vector3(30.0, 0.035, 1.75), Vector3(0, 0.198, 9.15),
|
||||
Color(0.20, 0.18, 0.17), false, null, Vector2.ONE, "stone")
|
||||
Color(0.34, 0.30, 0.27), false, T_CONCRETE, Vector2(18, 1.05), "concrete")
|
||||
var star_mesh := _star_mesh(0.40, 0.17)
|
||||
var brass := Materials.flat(Color(0.70, 0.55, 0.24), "metal")
|
||||
var coral := Materials.flat(Color(0.72, 0.30, 0.28), "stone")
|
||||
@@ -1595,6 +1603,8 @@ func _build_skyline_backdrop() -> void:
|
||||
mat.shader = shader
|
||||
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("haze_color", SKYLINE_BY_PHASE[_phase]["haze"])
|
||||
mat.set_shader_parameter("haze_strength", SKYLINE_BY_PHASE[_phase]["haze_strength"])
|
||||
mat.set_shader_parameter("repeats", 2.0)
|
||||
_skyline_mat = mat
|
||||
var mesh := PlaneMesh.new()
|
||||
@@ -1633,11 +1643,18 @@ 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 vec4 haze_color : source_color = vec4(0.62, 0.71, 0.82, 1.0);
|
||||
uniform float haze_strength = 0.36;
|
||||
uniform float repeats = 2.0;
|
||||
|
||||
void fragment() {
|
||||
vec4 panorama = texture(sky_texture, vec2(UV.x * repeats, UV.y));
|
||||
ALBEDO = panorama.rgb * tint.rgb;
|
||||
// Atmospheric perspective: blend the lower part of the panorama toward
|
||||
// the horizon colour so the towers sit in the distance instead of reading
|
||||
// as a crisp cardboard cut-out.
|
||||
float world_y = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).y;
|
||||
float horizon = clamp(1.0 - world_y / 42.0, 0.0, 1.0);
|
||||
ALBEDO = mix(panorama.rgb * tint.rgb, haze_color.rgb, horizon * haze_strength);
|
||||
ALPHA = panorama.a * tint.a;
|
||||
ALPHA_SCISSOR_THRESHOLD = 0.5;
|
||||
}
|
||||
@@ -2241,6 +2258,8 @@ func set_phase(phase: int) -> void:
|
||||
if _skyline_mat != null:
|
||||
_skyline_mat.set_shader_parameter("sky_texture", entry["texture"])
|
||||
_skyline_mat.set_shader_parameter("tint", entry["tint"])
|
||||
_skyline_mat.set_shader_parameter("haze_color", entry["haze"])
|
||||
_skyline_mat.set_shader_parameter("haze_strength", entry["haze_strength"])
|
||||
var night: bool = phase == Rules.Phase.EVENING or phase == Rules.Phase.LATE
|
||||
set_night_mode(night)
|
||||
if _street_mat != null:
|
||||
|
||||
@@ -62,6 +62,18 @@ const SHOTS_JOKE_DAY := [
|
||||
const SHOTS_STUDIO := [
|
||||
{"name": "studio_door", "pos": Vector3(0, 0.1, 6.2), "yaw": 0.0, "pitch": 2.0},
|
||||
]
|
||||
## Surface close-ups for the 0.9.5 texture audit; HUD is hidden for this stage.
|
||||
const SHOTS_SURFACES := [
|
||||
{"name": "surface_pavers", "pos": Vector3(0, 0.1, 10.5), "yaw": 0.0, "pitch": -55.0},
|
||||
{"name": "surface_asphalt", "pos": Vector3(0, 0.1, 14.0), "yaw": 180.0, "pitch": -42.0},
|
||||
{"name": "surface_limestone", "pos": Vector3(0, 0.1, 2.0), "yaw": 0.0, "pitch": 8.0},
|
||||
{"name": "surface_facade_wide", "pos": Vector3(-2.0, 0.1, 13.2), "yaw": 12.0, "pitch": 20.0},
|
||||
]
|
||||
const SHOTS_SURFACES_JOKE := [
|
||||
{"name": "surface_awning", "pos": Vector3(-22.6, 0.1, 2.6), "yaw": 0.0, "pitch": 14.0},
|
||||
{"name": "surface_canopy", "pos": Vector3(0, 0.1, 7.5), "yaw": 0.0, "pitch": 24.0},
|
||||
{"name": "surface_pavers_joke", "pos": Vector3(0, 0.1, 10.0), "yaw": 0.0, "pitch": -55.0},
|
||||
]
|
||||
## A face-on portrait: staged relative to a live NPC by `_stage_portrait()`.
|
||||
const SHOTS_PORTRAIT := [
|
||||
{"name": "npc_portrait", "pos": Vector3.ZERO, "yaw": 0.0, "pitch": 0.0,
|
||||
@@ -70,6 +82,8 @@ const SHOTS_PORTRAIT := [
|
||||
## 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": "surfaces", "elapsed": 0.0, "venue": "", "shots": SHOTS_SURFACES, "night": false},
|
||||
{"name": "surfaces_joke", "elapsed": 0.0, "venue": "joke_shop", "shots": SHOTS_SURFACES_JOKE, "night": false},
|
||||
# Portrait next, while the day crowd is still on the street.
|
||||
{"name": "portrait", "elapsed": 0.0, "venue": "", "shots": SHOTS_PORTRAIT,
|
||||
"night": false},
|
||||
@@ -182,6 +196,7 @@ func _begin_stage() -> void:
|
||||
gm.hud.clear_log()
|
||||
gm.hud.set_target_label("")
|
||||
gm.hud.set_action_hint("")
|
||||
gm.hud.visible = not str(stage["name"]).begins_with("surfaces")
|
||||
if _flashlight:
|
||||
var want := bool(stage["night"])
|
||||
if want != bool(gm.flashlight_on):
|
||||
|
||||
@@ -113,8 +113,13 @@ func test_hollywood_uses_the_real_landmark_layout() -> bool:
|
||||
var banners := root.find_child("EventBanners", true, false)
|
||||
var d := TestUtils.check(banners != null and banners.get_child_count() >= 9,
|
||||
"three red event banners with their trim are present")
|
||||
var e := TestUtils.check(root.find_child("WalkOfFame", true, false) != null,
|
||||
"the Hollywood Walk of Fame strip is built")
|
||||
var walk := root.find_child("WalkOfFame", true, false)
|
||||
var walk_band := walk.get_child(0) as MeshInstance3D if walk != null else null
|
||||
var walk_material: Material = walk_band.mesh.surface_get_material(0) if walk_band != null else null
|
||||
var e := TestUtils.check(walk != null and walk_material != null
|
||||
and walk_material is StandardMaterial3D
|
||||
and (walk_material as StandardMaterial3D).albedo_texture != null,
|
||||
"the Hollywood Walk of Fame strip is built with the terrazzo texture")
|
||||
var f := TestUtils.check(root.get_node_or_null("NeighbourBlocks/ElCapitanTheatre") != null
|
||||
and root.get_node_or_null("NeighbourBlocks/EastRetailBlock") != null,
|
||||
"the El Capitan Theatre and east retail neighbours are built")
|
||||
@@ -260,8 +265,12 @@ func test_skyline_backdrop_uses_alpha() -> bool:
|
||||
var mat: ShaderMaterial = builder._skyline_mat
|
||||
var b := TestUtils.check(mat != null and mat.shader != null, "skyline shader material captured")
|
||||
var code := mat.shader.code if mat != null and mat.shader != null else ""
|
||||
var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled"),
|
||||
"backdrop shader is alpha-scissored and fog-free")
|
||||
var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled")
|
||||
and code.contains("haze_strength"),
|
||||
"backdrop shader is alpha-scissored, fog-free and haze-blended")
|
||||
var haze := float(mat.get_shader_parameter("haze_strength")) if mat != null else 0.0
|
||||
var c2 := TestUtils.check(haze > 0.0, "skyline haze is enabled")
|
||||
c = c and c2
|
||||
var day: Texture2D = mat.get_shader_parameter("sky_texture")
|
||||
builder.set_phase(Rules.Phase.LATE)
|
||||
var night: Texture2D = mat.get_shader_parameter("sky_texture")
|
||||
|
||||
Reference in new issue
Block a user