diff --git a/MEMORY.md b/MEMORY.md index 40ae876..eb30c84 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -173,6 +173,6 @@ The playable build covers the three venues, tutorial/campaign/checkpoints, the 0 polish/texture/integration passes and the Blender + realism asset pipeline (see `DONE.md` and `CHANGELOG.md`). Active work is the unversioned **3D model and local map re-evaluation** track, then character development, then the 0.10.0 release candidate — `TODO.md` is the priority source. -Test counts as of 2026-11-03: Godot 276/276, Python 160/160, `check_docs` OK. +Test counts as of 2026-11-03: Godot 277/277, Python 160/160, `check_docs` OK. diff --git a/TODO.md b/TODO.md index 65cbf36..5d68c9f 100644 --- a/TODO.md +++ b/TODO.md @@ -200,7 +200,9 @@ licence-free zone only. board recaptured. Note: fix 2 (blade sign) is already a circular disc on a roof pole in the code (`37f925c`); the ranking note was stale. - [ ] Lighting: `lighting`/`lookdev` pass on sconces, canopy, marquee and street lamps with - day/late captures; verify local attenuation stays at 1.0 and no constant-falloff light returns. + day/late captures. (Attenuation is now gated: `test_venue_lights_use_physical_falloff` + asserts every `OmniLight3D`/`SpotLight3D` in all three venues uses `attenuation = 1.0`, + so a constant-falloff light cannot return unnoticed.) - [ ] Set dressing: review entrance and sidewalk prop clusters (`set-dressing`) for occlusion, navigation obstacles and readable storytelling clusters. - [x] Camera height: already decided in the 0.9.0 pass — keep the 1.7 m gameplay camera; the @@ -594,7 +596,7 @@ must inherit the reviewed face/hand/gear baseline instead of multiplying whateve Unversioned housekeeping that keeps the planning sources trustworthy. Do these when they block a task, not on a schedule. -**Status (2026-11-03):** v0.10.0-rc.1 source; three venues; 276 headless + 160 Python tests; the +**Status (2026-11-03):** v0.10.0-rc.1 source; three venues; 277 headless + 160 Python tests; the Hollywood and Joke Shop facade-realism passes landed (`limestone_ashlar`, `dark_stucco`, banner night emissive, marquee emissive test); reference boards (`hollywood`, `joke-shop`) with sidecars; three-up boards; `tools/refboard.py` + reference-anchored generation (`--reference`, `anchor`) + @@ -614,7 +616,7 @@ three-up boards; `tools/refboard.py` + reference-anchored generation (`--referen - [x] Fix stale status claims as they are found; add a documentation check or grep note only if the same class of drift returns. (Counts and the sign-in state corrected on 2026-11-03.) - [x] Keep the Gap Analysis baseline and the README status line in sync when a test count, version - or feature milestone changes (the baseline was bumped to 276/160 on 2026-11-03). + or feature milestone changes (the baseline was bumped to 277/160 on 2026-11-03). ### Imagery and provenance upkeep @@ -660,7 +662,7 @@ three-up boards; `tools/refboard.py` + reference-anchored generation (`--referen Baseline: **v0.9.6** (2026-11-03). Three playable venues, tutorial/campaign/checkpoint resume, difficulty/help, audio/accessibility settings, a 24-NPC budget baseline, a complete -escalation/access/radio loop, shift review, the Blender asset library, 276 headless + 160 Python +escalation/access/radio loop, shift review, the Blender asset library, 277 headless + 160 Python tests, a 21/21 clean screen-design judge and an optional Forward+ Desktop HQ build. This is the baseline; 0.10.0-rc.1 is the current candidate source, with release approvals still open. diff --git a/src/test/tests/test_world.gd b/src/test/tests/test_world.gd index 87f30dd..d8330c2 100644 --- a/src/test/tests/test_world.gd +++ b/src/test/tests/test_world.gd @@ -388,6 +388,24 @@ func test_library_dressing_collision_matches_the_documented_exemptions() -> bool return TestUtils.check(problems.is_empty(), "library dressing is covered: %s" % ", ".join(problems.slice(0, 6))) +func test_venue_lights_use_physical_falloff() -> bool: + var problems: Array[String] = [] + for venue in ["hollywood_building", "joke_shop", "tv_studio"]: + var root := Node3D.new() + var builder := WorldBuilder.new(root, venue) + builder.build() + for light in root.find_children("*", "OmniLight3D", true, false): + if not is_equal_approx((light as OmniLight3D).omni_attenuation, 1.0): + problems.append("%s/%s omni_attenuation=%.2f" % [venue, light.name, + (light as OmniLight3D).omni_attenuation]) + for light in root.find_children("*", "SpotLight3D", true, false): + if not is_equal_approx((light as SpotLight3D).spot_attenuation, 1.0): + problems.append("%s/%s spot_attenuation=%.2f" % [venue, light.name, + (light as SpotLight3D).spot_attenuation]) + root.free() + return TestUtils.check(problems.is_empty(), + "every venue light uses physical falloff: %s" % ", ".join(problems.slice(0, 6))) + func _walk_blocking_body(node: Node) -> StaticBody3D: for child in node.find_children("*", "StaticBody3D", true, false): var body := child as StaticBody3D