Files
gig-line/Tools/ue_placement.py
T
zwitschi 92b477d340 feat(hero): fluting and a recessed motto band; placement gets one source of truth
Hero detail:
- Fluting: 20 grooves per column as thin shadow-tone cylinders set into the shaft
  (120 pieces) rather than boolean cuts - readable in silhouette and grazing light
  at a fraction of the cost.
- Motto band: real recess with a moulded surround 6 cm proud and the panel 12 cm
  behind, replacing the 8 cm dark stripe.
  Hero 148 parts/3044 tris -> 271/5492, still 14.50 m.

Placement bug this exposed (a real one, invisible in a render):
- ue_hero_facade_import.py carried its own hero position (-12024.5, -1450) from
  before the 0.1.0 frontage fix, so running the importer alone moved the hero 13 m
  east and onto the north side of the boulevard. Only symptom: the reference score
  fell 0.69 -> 0.56 and the 3x3 composition correlation flipped +0.54 -> -0.08.
- New Tools/ue_placement.py is the single source of truth (hero position, verified
  frontage spans, span validators). Both the importer and the demo builder use it;
  the import asserts the staged span and logs PLACEMENT ERROR otherwise.
- 11 new tests, including one that fails if the stale literal returns. 78 total.

Spawn-view score recovered to 0.72 (best yet); screenshots verified 4/4, packaged QA
all PASS.
2026-11-08 22:12:44 +01:00

66 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Single source of truth for where staged actors belong in the DemoMap.
This exists because the same value was written in two places and they drifted:
`ue_hero_facade_import.py` kept the pre-`0.1.0` position `(-12024.5, -1450)` while
`ue_build_demo_map.py` staged the hero at `(-13336, 1307)`. Running the importer on
its own therefore moved the hero **13 m east and onto the north side of the
boulevard** — and nothing complained, because a displaced building still renders.
The values are Unreal centimetres, absolute world space. Every one of them is
verified against `Geo/verification/2026-11-06-el-capitan-block.md` and probed by
`Tools/visual_loop.py --verify` (via the demo screenshot expectations) and by the
world-span probes recorded in `MEMORY.md`.
Keep this file boring: constants and one validator, no `unreal` import, so it can
be unit-tested on the host.
"""
from __future__ import annotations
# The hero façade sits INSIDE the frontage gap between 6902 (ends at -17749) and
# the El Capitan Theatre (starts at -12082), so its actor origin is the measured
# building line. The mesh is authored with its front plane 7 cm ahead of the actor
# origin, which is why the X here is the frontage centre and Y is the building line.
HERO_ACTOR = "Hero_Facade"
HERO_LOCATION_CM = (-13336.0, 1307.0, 0.0)
# Tolerance for the span probe: the verified footprints are within ~1 m.
SPAN_TOLERANCE_CM = 100.0
# Expected world span per frontage actor (min X, max X), from the block
# verification notes. Used to prove a staged building is on its own footprint
# rather than merely present.
FRONTAGE_SPANS_CM = {
"6922": (-24276.0, -17700.0),
"6902": (-17749.0, -14591.0),
"hero": (-14644.0, -12028.0),
"6838": (-12082.0, -8210.0),
"6822": (-7854.0, -7252.0),
"6806": (-6608.0, -3307.0),
"6800": (-3320.0, -1437.0),
}
def span_error(name: str, observed_min: float, observed_max: float):
"""Return (expected_min, expected_max, ok) for an observed building span.
`name` is a key of `FRONTAGE_SPANS_CM`; `ok` is False when either edge is more
than `SPAN_TOLERANCE_CM` from the verified value.
"""
if name not in FRONTAGE_SPANS_CM:
raise KeyError(f"unknown building {name!r}; known: {sorted(FRONTAGE_SPANS_CM)}")
exp_min, exp_max = FRONTAGE_SPANS_CM[name]
ok = (abs(observed_min - exp_min) <= SPAN_TOLERANCE_CM
and abs(observed_max - exp_max) <= SPAN_TOLERANCE_CM)
return exp_min, exp_max, ok
def describe_span_failure(name: str, observed_min: float, observed_max: float) -> str:
"""A one-line message for a misplaced building, or "" when it is correct."""
exp_min, exp_max, ok = span_error(name, observed_min, observed_max)
if ok:
return ""
return (f"{name}: span X {observed_min:.0f}..{observed_max:.0f} does not match the "
f"verified {exp_min:.0f}..{exp_max:.0f} (±{SPAN_TOLERANCE_CM:.0f} cm)")