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.
This commit is contained in:
1 parent
bc5da3a8ab
commit
92b477d340
17 files changed
+255
-25
No files matched your search
@@ -0,0 +1,86 @@
|
||||
"""Tests for Tools/ue_placement.py — the single source of truth for placement.
|
||||
|
||||
A regression test for a real bug: `ue_hero_facade_import.py` carried its own hero
|
||||
position (the pre-0.1.0 one) while `ue_build_demo_map.py` staged the verified one.
|
||||
Running the importer moved the hero 13 m east and onto the north side of the
|
||||
boulevard, and nothing complained because a displaced building still renders.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
sys.path.insert(0, os.path.join(ROOT, "Tools"))
|
||||
|
||||
import ue_placement # noqa: E402
|
||||
|
||||
|
||||
class HeroPlacementTest(unittest.TestCase):
|
||||
def test_hero_actor_name(self):
|
||||
self.assertEqual(ue_placement.HERO_ACTOR, "Hero_Facade")
|
||||
|
||||
def test_hero_location_is_the_verified_building_line(self):
|
||||
x, y, z = ue_placement.HERO_LOCATION_CM
|
||||
self.assertEqual((x, y, z), (-13336.0, 1307.0, 0.0))
|
||||
# North is -Y in Unreal, so the hero on the south side must have Y > 0.
|
||||
self.assertGreater(y, 0.0, "hero must be on the south side (Y > 0)")
|
||||
|
||||
def test_hero_span_is_verified(self):
|
||||
# The mesh spans -1308..+1308 cm in X about its origin.
|
||||
lo = ue_placement.HERO_LOCATION_CM[0] - 1308.0
|
||||
hi = ue_placement.HERO_LOCATION_CM[0] + 1308.0
|
||||
exp_min, exp_max, ok = ue_placement.span_error("hero", lo, hi)
|
||||
self.assertTrue(ok)
|
||||
# The mesh spans exactly the verified footprint, so the two agree.
|
||||
self.assertAlmostEqual(exp_min, lo, delta=ue_placement.SPAN_TOLERANCE_CM)
|
||||
self.assertAlmostEqual(exp_max, hi, delta=ue_placement.SPAN_TOLERANCE_CM)
|
||||
|
||||
|
||||
class SpanValidatorTest(unittest.TestCase):
|
||||
def test_misplaced_hero_is_reported(self):
|
||||
# The exact failure observed: the stale import position put the hero here.
|
||||
lo, hi = -13332.0, -10716.0
|
||||
exp_min, exp_max, ok = ue_placement.span_error("hero", lo, hi)
|
||||
self.assertFalse(ok)
|
||||
message = ue_placement.describe_span_failure("hero", lo, hi)
|
||||
self.assertIn("does not match the verified", message)
|
||||
self.assertIn(f"{exp_min:.0f}", message)
|
||||
|
||||
def test_correct_span_reports_nothing(self):
|
||||
self.assertEqual(ue_placement.describe_span_failure("hero", -14644, -12028), "")
|
||||
|
||||
def test_within_tolerance_passes(self):
|
||||
# 99 cm off is inside the ±100 cm tolerance.
|
||||
self.assertTrue(ue_placement.span_error("6922", -24276 + 99, -17700 - 99)[2])
|
||||
|
||||
def test_outside_tolerance_fails(self):
|
||||
self.assertFalse(ue_placement.span_error("6922", -24276 + 101, -17700)[2])
|
||||
|
||||
def test_unknown_building_raises(self):
|
||||
with self.assertRaises(KeyError):
|
||||
ue_placement.span_error("no-such-building", 0, 0)
|
||||
|
||||
def test_every_frontage_span_is_ordered(self):
|
||||
for name, (lo, hi) in ue_placement.FRONTAGE_SPANS_CM.items():
|
||||
self.assertLess(lo, hi, f"{name} span is inverted")
|
||||
|
||||
|
||||
class SingleSourceOfTruthTest(unittest.TestCase):
|
||||
"""The import script and the demo builder must not carry their own literals."""
|
||||
|
||||
def test_import_script_uses_the_shared_constant(self):
|
||||
src = open(os.path.join(ROOT, "Tools", "ue_hero_facade_import.py"),
|
||||
encoding="utf-8").read()
|
||||
self.assertIn("ue_placement.HERO_LOCATION_CM", src)
|
||||
self.assertNotIn("(-12024.5, -1450.0, 0.0)", src,
|
||||
"the stale pre-0.1.0 hero position must not come back")
|
||||
|
||||
def test_demo_builder_uses_the_shared_constant(self):
|
||||
src = open(os.path.join(ROOT, "Tools", "ue_build_demo_map.py"),
|
||||
encoding="utf-8").read()
|
||||
self.assertIn("ue_placement.HERO_LOCATION_CM", src)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user