Visual polish pass (v0.2.0): phase lighting, street lamps, marquee, dressing, night screenshots

- Phase-based lighting: sun direction/energy/color, ProceduralSkyMaterial
  gradient, and ambient per shift phase (GameManager.PHASE_LIGHTING),
  applied on phase transitions and shift start
- WorldBuilder: street lamp poles with emissive bulbs + OmniLights,
  set_night_mode(), glowing STEP LINE marquee (Label3D + emissive bars),
  crosswalk stripes, planters, low-poly street trees
- NPC avatars: per-seed height/brightness variation (deterministic LCG)
- Denser routine traffic probabilities
- capture_screenshots.gd: fast-forward to LATE phase and capture night set;
  README gallery is now 2x3 day+night; docs/screenshots regenerated
- Version bumped to 0.2.0; build/ artifacts rebuilt; Linux binary smoke-tested
- New headless tests for phased lighting config and street dressing (56 total)
This commit is contained in:
zwitschi committed 2026-10-31 13:24:40 +01:00
1 parent 93883334d0
commit 513f8a0f60
21 files changed
+280 -25

No files matched your search

+22 -5
View File
@@ -1,10 +1,11 @@
extends SceneTree
## Dev helper: run the game windowed and capture staged screenshots for the
## README. Saves 1600x900 PNGs to user://screenshots/.
## README (day set + LATE-night set with street lamps and flashlight).
## Saves 1280x720 PNGs to user://screenshots/.
## Usage: godot --path . --script res://src/test/capture_screenshots.gd
const OUT_DIR := "user://screenshots"
const MAX_FRAMES := 2400 # hard cap (~40s)
const MAX_FRAMES := 3600 # hard cap (~60s)
var frames := 0
var gm: Node = null
@@ -39,7 +40,7 @@ func _advance() -> void:
state_frames += 1
match state:
0:
# 2s overview from the guard post.
# 2s overview from the guard post (early morning light).
if state_frames >= 120:
_shoot("01-venue-overview.png")
state = 1
@@ -52,7 +53,6 @@ func _advance() -> void:
state = 2
state_frames = 0
2:
# Give the HUD a moment, then shoot the interaction shot.
if state_frames >= 30:
_shoot("02-targeting-npc.png")
if targeted and gm != null:
@@ -64,7 +64,6 @@ func _advance() -> void:
_shoot("03-access-inspection.png")
if gm != null:
gm.hud.hide_inspection()
# Re-target (target may have moved) before opening the radio form.
if gm.npcs.size() > 0:
gm.call("_cycle_nearby_target")
gm.call("_open_radio", false)
@@ -73,7 +72,25 @@ func _advance() -> void:
4:
if state_frames >= 30:
_shoot("04-radio-report.png")
# Close the radio form, then jump the shift to the LATE phase.
if gm != null:
gm.radio_panel.hide()
gm.coordinator.elapsed_s = 750.0
state = 5
state_frames = 0
5:
# Let the phase transition (and street lamps / marquee) settle, then
# take the night avenue shot.
if state_frames >= 90:
_shoot("05-late-night-avenue.png")
if gm != null:
gm.call("_toggle_flashlight")
state = 6
state_frames = 0
6:
if state_frames >= 40:
_shoot("06-night-flashlight.png")
state = 7
quit(0)
func _shoot(file_name: String) -> void:
+29 -1
View File
@@ -1,7 +1,9 @@
extends RefCounted
## Headless tests for the procedural world and navigation mesh.
## Headless tests for the procedural world, navigation mesh, and phase lighting.
const WorldBuilder := preload("res://src/game/world_builder.gd")
const Rules := preload("res://src/core/rules.gd")
const GameManager := preload("res://src/game/game_manager.gd")
func test_navmesh_built() -> bool:
var root := Node3D.new()
@@ -17,6 +19,32 @@ func test_navmesh_built() -> bool:
root.free()
return a and b
func test_phase_lighting_config_covered() -> bool:
var ok := true
for phase in [Rules.Phase.EARLY, Rules.Phase.MIDDAY, Rules.Phase.EVENING, Rules.Phase.LATE]:
var cfg: Variant = GameManager.PHASE_LIGHTING.get(phase, null)
if not (cfg is Dictionary):
ok = false
continue
for key in ["sun_energy", "sun_angle", "sun_color", "sky_top", "sky_horizon", "ambient", "night"]:
if not (cfg as Dictionary).has(key):
ok = false
return TestUtils.check(ok, "all four phases have a full lighting config")
func test_street_lamps_and_marquee_built() -> bool:
var root := Node3D.new()
var builder := WorldBuilder.new(root)
builder.build()
var a := TestUtils.check(builder._street_lamps.size() >= 2, "street lamps built")
var b := TestUtils.check(builder.marquee_label != null, "marquee label built")
# Night mode raises bulb emission; day mode drops it.
builder.set_night_mode(true)
var night_on := builder._lamp_bulb_mats[0].emission_energy_multiplier > 1.0
builder.set_night_mode(false)
var day_off := builder._lamp_bulb_mats[0].emission_energy_multiplier <= 0.5
root.free()
return a and b and TestUtils.check(night_on, "lamps brighten at night") and TestUtils.check(day_off, "lamps dim in daylight")
func test_door_indicator_built() -> bool:
var root := Node3D.new()
var builder := WorldBuilder.new(root)