diff --git a/CHANGELOG.md b/CHANGELOG.md index a97561e..429d8a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Semantic Versioning. ### Fixed +- The red event banners are lit from the top down at night instead of reading as flat red cards. - Street lamps now throw a warm pool on the pavement instead of dimly washing the whole walkway. - The flashlight actually lights what it points at. It was tuned to a beam so weak it changed the frame by less than one 255th of a level; it now throws a visible pool on the door and the pavement diff --git a/MEMORY.md b/MEMORY.md index 56e74c2..ed0d81e 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -130,6 +130,10 @@ canonical implemented-features list in `docs/project/development-status.md`. gear library. The procedural library + MakeHuman head stay in the tree as the tested fallback (`build --source procedural`), not in the shipped roster. Silhouette variety is the known regression — three meshes for twelve roles — and the modular part library is the `0.9.10` fix. +- **Emissive materials can carry a per-material night energy.** `set_night_mode` used to write one + multiplier into every `_marquee_emissive` entry, which flattens any gradient. It now reads + `mat.get_meta("night_energy", 2.0)`, so a multi-strip panel can keep its top-to-bottom ratio + (the event banners: 2.6 → 1.4). - **Venue lights must keep `omni_attenuation == 1.0`** — `test_world.test_venue_lights_use_physical_falloff` enforces physical falloff on every Omni/Spot in all three venues, so a "pool" cannot come from sharpening the curve. Use `omni_range` + energy instead: 10 m / energy 8 gives a brighter pool and diff --git a/TODO.md b/TODO.md index 42fcdf0..5acd9bd 100644 --- a/TODO.md +++ b/TODO.md @@ -961,8 +961,10 @@ scorecard source, this milestone is the actionable list. 20 m range and a 14° cone changed the frame by **0.1/255**. Now 10 energy, 45 m, 24°, tilted 5° down — the aimed region goes 13.5 → 29.7 and the near pavement 3.7 → 11.7 (0–255), and the committed `06-night-flashlight` shot shows the beam on the steps and the NPC in front. -- [ ] Marquee/event-banner emissive read under the Compatibility renderer: measure the banner - luminance against the reference night board and adjust only if it still clips. +- [x] **Event banners read as lit cloth.** Measured flat top-to-bottom (114 → 125/255) with no + clipping, so the problem was not brightness but the missing gradient: four stacked strips + with night energies 2.6/2.2/1.8/1.4 now run 135 → 84, carried per material via `night_energy` + metadata so `set_night_mode` keeps the ratio. Daylight is unchanged and nothing clips. - [x] **Street-lamp pools.** Added a `lamp_late` shot that frames a lamp, and made the lamps `StreetLamp_*` so they tune apart from the sconces. Attenuation must stay 1.0 (the venue test requires physical falloff), so the pool comes from range and energy: 16 m/2.2 → **10 m/8.0** diff --git a/docs/assets/3d-model-review.md b/docs/assets/3d-model-review.md index 7b1318f..e2116d5 100644 --- a/docs/assets/3d-model-review.md +++ b/docs/assets/3d-model-review.md @@ -334,7 +334,21 @@ and energy: Mean luminance 0–255. The shipped values brighten the pool past the old setting while darkening the gap between poles, which is what "pools, not a wash" means numerically. -Still open from the milestone: marquee emissive under Compatibility. +### Event banners read as lit cloth (2026-11-06) + +The banners were one flat emissive panel, so they measured the same luminance from the mounting bar +to the hem (114/121/122/125 down the panel) and read as a painted red card rather than lit fabric. +They are now four stacked strips with a night gradient (energies 2.6 / 2.2 / 1.8 / 1.4), carried per +material through `night_energy` metadata so `set_night_mode` keeps the ratio instead of flattening it. + +| | Before | After | +| --- | --- | --- | +| Vertical profile down a banner | 114 → 125 (flat) | 135 → 84 | +| Clipping (p99 / pixels ≥ 250) | 192 / 0.2 % | 192 / 0.2 % | +| Daylight read | uniform | uniform (strips flush, no seam) | + +No clipping in either state, and the Compatibility renderer draws it with the same flat emissive +material as before — the change is geometry plus per-material energy, not a new shader. ## Scorecard skeleton diff --git a/docs/screenshots/01-venue-overview.png b/docs/screenshots/01-venue-overview.png index be424c4..dc51dbd 100644 Binary files a/docs/screenshots/01-venue-overview.png and b/docs/screenshots/01-venue-overview.png differ diff --git a/docs/screenshots/02-targeting-npc.png b/docs/screenshots/02-targeting-npc.png index eebaa00..7a76b07 100644 Binary files a/docs/screenshots/02-targeting-npc.png and b/docs/screenshots/02-targeting-npc.png differ diff --git a/docs/screenshots/03-access-inspection.png b/docs/screenshots/03-access-inspection.png index 1802d57..e17a015 100644 Binary files a/docs/screenshots/03-access-inspection.png and b/docs/screenshots/03-access-inspection.png differ diff --git a/docs/screenshots/04-radio-report.png b/docs/screenshots/04-radio-report.png index 9810900..e1f963e 100644 Binary files a/docs/screenshots/04-radio-report.png and b/docs/screenshots/04-radio-report.png differ diff --git a/docs/screenshots/05-late-night-avenue.png b/docs/screenshots/05-late-night-avenue.png index 16ad1e8..382fc54 100644 Binary files a/docs/screenshots/05-late-night-avenue.png and b/docs/screenshots/05-late-night-avenue.png differ diff --git a/docs/screenshots/06-night-flashlight.png b/docs/screenshots/06-night-flashlight.png index 54f4038..48200df 100644 Binary files a/docs/screenshots/06-night-flashlight.png and b/docs/screenshots/06-night-flashlight.png differ diff --git a/docs/screenshots/08-escalation-hud.png b/docs/screenshots/08-escalation-hud.png index fb12264..4ec46ef 100644 Binary files a/docs/screenshots/08-escalation-hud.png and b/docs/screenshots/08-escalation-hud.png differ diff --git a/docs/screenshots/09-body-camera-timeline.png b/docs/screenshots/09-body-camera-timeline.png index ebc682c..195fa78 100644 Binary files a/docs/screenshots/09-body-camera-timeline.png and b/docs/screenshots/09-body-camera-timeline.png differ diff --git a/docs/screenshots/11-precision-walk.png b/docs/screenshots/11-precision-walk.png index 4f7c7b3..26fe92e 100644 Binary files a/docs/screenshots/11-precision-walk.png and b/docs/screenshots/11-precision-walk.png differ diff --git a/docs/screenshots/12-joke-shop.png b/docs/screenshots/12-joke-shop.png index 983d143..85c037d 100644 Binary files a/docs/screenshots/12-joke-shop.png and b/docs/screenshots/12-joke-shop.png differ diff --git a/docs/screenshots/13-studio-arrival.png b/docs/screenshots/13-studio-arrival.png index 29184ac..4aade2c 100644 Binary files a/docs/screenshots/13-studio-arrival.png and b/docs/screenshots/13-studio-arrival.png differ diff --git a/docs/screenshots/17-shift-review.png b/docs/screenshots/17-shift-review.png index d5b8436..28520ef 100644 Binary files a/docs/screenshots/17-shift-review.png and b/docs/screenshots/17-shift-review.png differ diff --git a/docs/screenshots/19-radial-menu.png b/docs/screenshots/19-radial-menu.png index 790c869..9a7609e 100644 Binary files a/docs/screenshots/19-radial-menu.png and b/docs/screenshots/19-radial-menu.png differ diff --git a/docs/screenshots/20-crowd-panic.png b/docs/screenshots/20-crowd-panic.png index 5ec111b..765a7ef 100644 Binary files a/docs/screenshots/20-crowd-panic.png and b/docs/screenshots/20-crowd-panic.png differ diff --git a/docs/screenshots/21-responder-arrival.png b/docs/screenshots/21-responder-arrival.png index 1bc9470..70b4407 100644 Binary files a/docs/screenshots/21-responder-arrival.png and b/docs/screenshots/21-responder-arrival.png differ diff --git a/src/game/world_builder.gd b/src/game/world_builder.gd index 5078d84..c20a60d 100644 --- a/src/game/world_builder.gd +++ b/src/game/world_builder.gd @@ -1159,10 +1159,21 @@ func _add_masonic_banner(parent: Node3D, x: float, front: float, text: String) - # The reference night photo keeps the event banners readable against the dark # stone: a low warm self-light that the day/night cycle brightens (not a # glowing neon box). Registered with `_marquee_emissive` in `set_night_mode`. - var cloth := _add_box(parent, Vector3(2.62, 5.55, 0.06), Vector3(x, 8.2, front + 0.73), - red, true, null, Vector2.ONE, "cloth") - cloth.material_override = Materials.emissive(red, 0.14) - _marquee_emissive.append(cloth.material_override) + # Four stacked strips so the banner is brightest at the top, where the mounting bar + # and the wash lights are, and falls off towards the hem. A single flat emissive + # panel measured the same luminance top to bottom and read as a painted card + # (measured: 114/121/122/125 down the panel before this). + var strips := 4 + var strip_height := 5.55 / float(strips) + var night_energy := [2.6, 2.2, 1.8, 1.4] + for index in strips: + var centre_y := 8.2 + 5.55 * 0.5 - strip_height * (float(index) + 0.5) + var strip := _add_box(parent, Vector3(2.62, strip_height, 0.06), + Vector3(x, centre_y, front + 0.73), red, true, null, Vector2.ONE, "cloth") + var strip_mat := Materials.emissive(red, 0.14) + strip_mat.set_meta("night_energy", float(night_energy[index])) + strip.material_override = strip_mat + _marquee_emissive.append(strip_mat) for y in [5.55, 10.85]: _add_box(parent, Vector3(2.82, 0.10, 0.12), Vector3(x, y, front + 0.75), Color(0.78, 0.62, 0.28), true, null, Vector2.ONE, "metal") @@ -2394,7 +2405,10 @@ func set_night_mode(night: bool) -> void: if _ovation_sign: _ovation_sign.modulate = Color(0.90, 0.84, 0.70) if night else Color(0.16, 0.14, 0.12) for mat in _marquee_emissive: - mat.emission_energy_multiplier = (2.0 if night else 0.2) + # Each marquee material can carry its own night energy: the event banners use a + # top-to-bottom gradient so they read as lit cloth instead of a red rectangle. + mat.emission_energy_multiplier = (float(mat.get_meta("night_energy", 2.0)) + if night else 0.2) # Light most, not all, of the windows: a fully lit grid reads as a light box # instead of an occupied building. for i in _window_mats.size():