test(visual): a shadow-gap measurement, and the spawn height cleared
Tools/shadow_gap.py measures the vertical pixel gap between an object's lowest pixels and the top of its shadow, as a fraction of the object's height, and calls it contact or floating. It classifies a shadow by per-channel scaling - a shadow darkens every channel by the same proportion, an object does not - which separates the two where an absolute delta cannot. Four unit tests cover contact, floating, an empty box and the border median. On the real spawn view it returns 'unknown': the crop contains two surfaces (pale wall behind, dark road below) and one global background cannot classify both. Recorded as the next step, not presented as working. The spawn height is measured and cleared: tracing down from 30 m, the topmost surface under the player is Z 366 and the capsule's feet are at 369 - 3 cm, the capsule skin. If the character still reads as floating, the gap is between the rendered pavement and its collision surface, not the spawn Z, and that is what to measure next.
This commit is contained in:
1 parent
dde451fd69
commit
f1d6283384
5 files changed
+230
-1
No files matched your search
@@ -0,0 +1,56 @@
|
||||
"""Tests for the pure parts of Tools/shadow_gap.py."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import shadow_gap as sg # noqa: E402
|
||||
|
||||
|
||||
def synthetic(object_bottom, shadow_top, size=(80, 120), background=(200, 200, 196)):
|
||||
"""A grey frame with a dark block (object) and a dark strip (shadow) below it."""
|
||||
image = Image.new("RGB", size, background)
|
||||
pixels = image.load()
|
||||
for y in range(20, object_bottom):
|
||||
for x in range(30, 50):
|
||||
pixels[x, y] = (18, 18, 20) # object: far darker than the surface
|
||||
for y in range(shadow_top, min(size[1] - 2, shadow_top + 12)):
|
||||
for x in range(30, 50):
|
||||
# shadow: a darkened version of the surface, as a real shadow is
|
||||
pixels[x, y] = (140, 140, 138)
|
||||
return image
|
||||
|
||||
|
||||
class MeasureTests(unittest.TestCase):
|
||||
def test_contact_reads_as_contact(self):
|
||||
image = synthetic(object_bottom=60, shadow_top=61)
|
||||
image.save("/tmp/gigline-shadow-contact.png")
|
||||
result = sg.measure("/tmp/gigline-shadow-contact.png", (0.2, 0.05, 0.8, 0.95))
|
||||
self.assertEqual(result["verdict"], "contact")
|
||||
self.assertLessEqual(result["gap_px"], 2)
|
||||
|
||||
def test_a_gap_reads_as_floating(self):
|
||||
image = synthetic(object_bottom=60, shadow_top=100)
|
||||
image.save("/tmp/gigline-shadow-float.png")
|
||||
result = sg.measure("/tmp/gigline-shadow-float.png", (0.2, 0.05, 0.8, 0.95))
|
||||
self.assertEqual(result["verdict"], "floating")
|
||||
self.assertGreater(result["gap_px"], result["object_height_px"] * 0.12)
|
||||
|
||||
def test_empty_box_reports_an_error(self):
|
||||
image = Image.new("RGB", (40, 40), (200, 200, 200))
|
||||
image.save("/tmp/gigline-shadow-empty.png")
|
||||
result = sg.measure("/tmp/gigline-shadow-empty.png", (0.1, 0.1, 0.9, 0.9))
|
||||
self.assertIn("error", result)
|
||||
|
||||
|
||||
class BorderTests(unittest.TestCase):
|
||||
def test_border_median_is_the_frame_colour(self):
|
||||
image = Image.new("RGB", (40, 40), (180, 190, 200))
|
||||
self.assertEqual(sg.border_median(image), (180, 190, 200))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user