diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa53aa..81be978 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ Semantic Versioning. ## [Unreleased] +### Fixed + +- 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 + ahead, in both venues. + ### Changed - Street greenery now reads as leaves instead of green rocks: the planter shrubs and tree crowns diff --git a/MEMORY.md b/MEMORY.md index 019d34f..52c2ac8 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -130,6 +130,16 @@ 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. +- **Tune lights against a measurement, not a look (2026-11-06):** the shipped flashlight changed the + late-night frame by 0.1/255 and nobody noticed. `capture_realism.gd --stages=late --flashlight` + renders the same frame with the lamp on and off; mean luminance of the aimed region and the near + pavement is now the acceptance number. Current: energy 10, range 45 m, cone 24°, tilted 5° down → + 13.5 → 29.7 aimed, 3.7 → 11.7 near ground. +- **Docs live in category folders (2026-11-06):** `docs/project/` (architecture, development status), + `docs/design/` (gameplay, venues, ui-copy, accessibility, known-limitations), `docs/assets/` + (asset tooling, blender assets, npc sourcing, 3d-model-review, makehuman head), `docs/process/` + (getting started, release QA, releasing), plus `docs/adr/`, `docs/screens/`, `docs/screenshots/`. + Write to the new paths; `docs/*.md` at the root no longer exists apart from `README.md`. - **Foliage is leaf cards, not sphere stacks (2026-11-06):** `_shrub()` is one low-poly inner mass plus 18–53 two-triangle cards scattered on the canopy shell by a jittered Fibonacci sequence, each rotated onto the canopy normal and tilted off it. No alpha texture, so the flat-colour library and diff --git a/TODO.md b/TODO.md index 2d0d7a8..6dc49c1 100644 --- a/TODO.md +++ b/TODO.md @@ -957,8 +957,17 @@ scorecard source, this milestone is the actionable list. - [ ] Joke Shop / Hollywood shape language: fix the ranked remaining items (west rounded corner, canopy pitch, facade reveal depth) only where a capture shows a readable gain. -- [ ] `lighting`/`lookdev` pass: flashlight cone/energy for the `06-night-flashlight` stage, - marquee/event-banner emissive read under the Compatibility renderer, street-lamp pools. +- [x] **Flashlight tuned by measurement.** The late-night capture proved it did nothing: energy 4.2, + 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. +- [ ] Street-lamp pools: the night ground sits at 3.8/255 away from the sconces. Raising the lamp + energy was rejected once for washing the sidewalk grey, so this needs a capture-driven + range/attenuation change rather than more energy. +- [x] Refresh `docs/screenshots/` for the whole roster and the new flashlight; `check_screenshots.sh` + reports 21 good / 0 bad. - [ ] Set dressing: fix the Joke Shop queue story (podium/stanchion line at the door), rope barrier thickness, the west palm overlap and the empty sidewalk light pool; apply the same review to Hollywood and the TV Studio. @@ -966,8 +975,7 @@ scorecard source, this milestone is the actionable list. ### Evidence and release -- [ ] Refresh `sheet_props.jpg`, `sheet_npc_gear.jpg`, the three-up boards, the affected venue - captures and the README asset rows. +- [x] `sheet_props.jpg` and `sheet_npc_gear.jpg` rebuilt; the 21-image venue gallery regenerated. - [ ] Re-run `tools/check_screenshots.sh` and `python3 tools/check_docs.py`; record both results. - [ ] Release `0.9.11` with the full procedure. diff --git a/docs/assets/3d-model-review.md b/docs/assets/3d-model-review.md index fa40b93..d2a95db 100644 --- a/docs/assets/3d-model-review.md +++ b/docs/assets/3d-model-review.md @@ -302,6 +302,25 @@ renderer are untouched. | `street_tree` | 2,592 tris | 1,506 | | Props total (20) | 13,808 | 11,784 | +## Lighting pass — flashlight measured 2026-11-06 + +`capture_realism.gd --stages=late --flashlight` renders the same frame with the lamp off and on, so +the beam's contribution is a number instead of an impression. Mean luminance, 0–255: + +| Region | Lamp off | Old settings | Shipped now | +| --- | --- | --- | --- | +| Aimed centre (facade) | 13.5 | 13.6 | **29.7** | +| Near pavement | 3.7 | 3.7 | **11.7** | + +The old flashlight (energy 4.2, 20 m range, 14° cone, aimed dead ahead) moved the frame by 0.1/255 — +it was effectively off. The shipped values are 10 energy, 45 m, 24°, tilted 5° down: the beam lands +on the door or person being checked *and* puts a pool on the pavement in front, without flooding the +facade. Verified in both venues (Hollywood facade and the Joke Shop door). + +Still open from the milestone: marquee emissive under Compatibility, and the street-lamp pools — the +night ground sits at 3.8/255 away from the sconces, and raising lamp energy was already rejected once +for washing the sidewalk grey, so those need range/attenuation work rather than more brightness. + ## Scorecard skeleton Verdicts use `qa-review` semantics: **P0** (blocker: wrong scale, broken normals, budget fail), diff --git a/docs/screenshots/01-venue-overview.png b/docs/screenshots/01-venue-overview.png index f7454eb..822de44 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 e60147b..c741842 100644 Binary files a/docs/screenshots/02-targeting-npc.png and b/docs/screenshots/02-targeting-npc.png differ diff --git a/docs/screenshots/05-late-night-avenue.png b/docs/screenshots/05-late-night-avenue.png index caedf42..a7ccbe2 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 57f20bc..99d7560 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 3657345..ad8a2fc 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 b242a74..e5ed13f 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/10-prop-gallery.png b/docs/screenshots/10-prop-gallery.png index 641d04a..b780e7f 100644 Binary files a/docs/screenshots/10-prop-gallery.png and b/docs/screenshots/10-prop-gallery.png differ diff --git a/docs/screenshots/11-precision-walk.png b/docs/screenshots/11-precision-walk.png index 1f7265e..311d19e 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 e9ad85a..3ea5af5 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 9b33a0e..e766a23 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 dd0fa77..02fef9c 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 b1d95e4..1833840 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 a093740..e598fb5 100644 Binary files a/docs/screenshots/21-responder-arrival.png and b/docs/screenshots/21-responder-arrival.png differ diff --git a/src/game/game_manager.gd b/src/game/game_manager.gd index a42811a..3cc1bb2 100644 --- a/src/game/game_manager.gd +++ b/src/game/game_manager.gd @@ -224,12 +224,17 @@ func _build_guard() -> GuardController: flashlight.name = "Flashlight" flashlight.visible = false flashlight.position = Vector3(0, -0.15, -0.4) - # Aim straight ahead: tilting it down made the cone hit the ground right in - # front of the guard instead of the person or door being checked. - flashlight.rotation_degrees = Vector3.ZERO - flashlight.spot_range = 30.0 if Materials.modern_light_falloff() else 20.0 - flashlight.spot_angle = 14.0 - flashlight.light_energy = 4.2 + # Aimed a few degrees down: straight ahead left the pavement in front of the guard + # black (night ground measured 3.7/255 with the lamp on), and tilting further hid + # the door or person being checked. + flashlight.rotation_degrees = Vector3(-5.0, 0.0, 0.0) + # Tuned against the late-night capture: the old 4.2 energy / 20 m range / 14 deg + # cone changed the frame by 0.1/255 — effectively an invisible flashlight. These + # values lift the aimed region from 13.5 to 29.7 and the near pavement from 3.7 to + # 11.7 without blowing the facade out. + flashlight.spot_range = 45.0 + flashlight.spot_angle = 24.0 + flashlight.light_energy = 10.0 flashlight.spot_attenuation = 1.0 flashlight.spot_angle_attenuation = 0.9 # The volumetric shaft is a Forward+ (desktop HQ) effect. Compatibility and diff --git a/src/test/tests/test_door_and_flashlight.gd b/src/test/tests/test_door_and_flashlight.gd index 2dc3052..a6f5597 100644 --- a/src/test/tests/test_door_and_flashlight.gd +++ b/src/test/tests/test_door_and_flashlight.gd @@ -38,16 +38,30 @@ func test_flashlight_aims_a_tight_forward_spot() -> bool: var guard: Node3D = gm.call("_build_guard") var lamp: SpotLight3D = guard.get_node_or_null("Head/Flashlight") as SpotLight3D var a := TestUtils.check(lamp != null, "flashlight lives on the guard's head") - var b := TestUtils.check(lamp != null and lamp.rotation_degrees.is_equal_approx(Vector3.ZERO), - "flashlight aims straight ahead") - var c := TestUtils.check(lamp != null and lamp.spot_angle <= 20.0, - "flashlight cone is narrow enough to read as a torch") + # The lamp is tilted a few degrees down so the beam also puts a pool on the + # pavement in front; straight ahead left that ground black. Anything past ~8 deg + # drops the door being checked out of the cone. + var b := TestUtils.check(lamp != null + and lamp.rotation_degrees.x <= 0.0 and lamp.rotation_degrees.x >= -8.0 + and is_zero_approx(lamp.rotation_degrees.y) and is_zero_approx(lamp.rotation_degrees.z), + "flashlight aims forward with a slight downward tilt (x %.1f)" % ( + lamp.rotation_degrees.x if lamp else 0.0)) + # Measured: the old 4.2 energy / 20 m / 14 deg beam changed the late-night frame by + # 0.1/255 and read as off. These bounds keep it a torch (not a floodlight) while + # still lighting what the guard looks at. + var c := TestUtils.check(lamp != null and lamp.spot_angle >= 20.0 + and lamp.spot_angle <= 30.0, + "flashlight cone reads as a torch (%.0f deg)" % (lamp.spot_angle if lamp else 0.0)) + var c2 := TestUtils.check(lamp != null and lamp.light_energy >= 8.0 + and lamp.spot_range >= 30.0, + "flashlight is bright enough to see (energy %.1f, range %.0f m)" % [ + lamp.light_energy if lamp else 0.0, lamp.spot_range if lamp else 0.0]) var d := TestUtils.check(lamp != null and not lamp.visible, "flashlight starts off") var e := TestUtils.check(lamp != null and is_zero_approx(lamp.light_volumetric_fog_energy), "headless/compatibility builds leave the flashlight non-volumetric") root.free() - return a and b and c and d and e + return a and b and c and c2 and d and e func test_flashlight_widens_perception() -> bool: ## Without the flashlight, an NPC at AWARE_RADIUS + 3 m is unaware.