Give the event banners a top-down night gradient so they read as lit cloth
Full build / Import + headless tests (push) Successful in 47s
Full build / Full desktop build + optional release (push) Skipped

This commit is contained in:
zwitschi committed 2026-11-06 20:33:31 +01:00
1 parent 7546092c01
commit 6381dbc5cb
20 files changed
+43 -8

No files matched your search

+1
View File
@@ -8,6 +8,7 @@ Semantic Versioning.
### Fixed ### 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. - 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 - 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 frame by less than one 255th of a level; it now throws a visible pool on the door and the pavement
+4
View File
@@ -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 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 (`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. 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` - **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 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 sharpening the curve. Use `omni_range` + energy instead: 10 m / energy 8 gives a brighter pool and
+4 -2
View File
@@ -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° 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 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. 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 - [x] **Event banners read as lit cloth.** Measured flat top-to-bottom (114 → 125/255) with no
luminance against the reference night board and adjust only if it still clips. 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 - [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 `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** requires physical falloff), so the pool comes from range and energy: 16 m/2.2 → **10 m/8.0**
+15 -1
View File
@@ -334,7 +334,21 @@ and energy:
Mean luminance 0–255. The shipped values brighten the pool past the old setting while darkening the 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. 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 ## Scorecard skeleton
Binary file not shown.

Before

Width:  |  Height:  |  Size: 899 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 740 KiB

After

Width:  |  Height:  |  Size: 731 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 KiB

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 757 KiB

After

Width:  |  Height:  |  Size: 757 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 KiB

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 KiB

After

Width:  |  Height:  |  Size: 969 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+19 -5
View File
@@ -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 # 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 # 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`. # 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), # Four stacked strips so the banner is brightest at the top, where the mounting bar
red, true, null, Vector2.ONE, "cloth") # and the wash lights are, and falls off towards the hem. A single flat emissive
cloth.material_override = Materials.emissive(red, 0.14) # panel measured the same luminance top to bottom and read as a painted card
_marquee_emissive.append(cloth.material_override) # (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]: for y in [5.55, 10.85]:
_add_box(parent, Vector3(2.82, 0.10, 0.12), Vector3(x, y, front + 0.75), _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") 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: if _ovation_sign:
_ovation_sign.modulate = Color(0.90, 0.84, 0.70) if night else Color(0.16, 0.14, 0.12) _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: 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 # Light most, not all, of the windows: a fully lit grid reads as a light box
# instead of an occupied building. # instead of an occupied building.
for i in _window_mats.size(): for i in _window_mats.size():