tools(verify): fast/medium/full verification tiers with a compiled-in source stamp (L39); research R6
BuildLinux.sh hashes a sorted manifest of compiled inputs into VOXELFORGE_SOURCE_STAMP, printed first by every executable; Standalone/Tools/verify/verify.py checks it before and after each tier and records binary/input hashes and the resolved recipe, style map and layout. Fast (163 s) runs on the canonical owner world only and asserts FI Style 2 / SW Style 1; medium (8 min) and full (26 min) add the style matrix, GCC, determinism and cost. Controls: stale source, mislabelled style-0 owner, all-black capture, L30/L31, L35/L36 and the L38 step are all rejected. No geometry change; owner hashes identical to L37. Research R6: leak table, generator contract and a 10-step metre-native migration plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
88c54eab44
commit
753cc9c259
22 files changed
+1831
-163
No files matched your search
@@ -25,7 +25,7 @@ FRAGMENT_MAX_AREA_M2 = 0.05
|
||||
FRAGMENT_MAX_DISTANCE_M = 12.0
|
||||
|
||||
|
||||
def small_components(triangles):
|
||||
def small_components(triangles, center=None):
|
||||
parent = list(range(len(triangles)))
|
||||
sizes = [1] * len(triangles)
|
||||
owners = {}
|
||||
@@ -67,7 +67,7 @@ def small_components(triangles):
|
||||
ab[0] * ac[1] - ab[1] * ac[0])
|
||||
item["area"] += 0.5 * math.sqrt(sum(value * value for value in cross))
|
||||
|
||||
cx, cy = CENTER
|
||||
cx, cy = center or CENTER
|
||||
small = []
|
||||
below_floor = []
|
||||
for item in components.values():
|
||||
@@ -81,12 +81,21 @@ def small_components(triangles):
|
||||
return len(components), small, below_floor
|
||||
|
||||
|
||||
def audit(path):
|
||||
cx, cy = CENTER
|
||||
bounds = (cx - OUTER, cx + OUTER, cy - OUTER, cy + OUTER)
|
||||
voxel_um, triangles, tiles = RING.read_scene(
|
||||
str(path), 0, HEIGHT_MIN, HEIGHT_MAX, bounds)
|
||||
component_count, fragments, below_floor = small_components(triangles)
|
||||
def audit(path, scene_data=None, center=None, height_min=None, height_max=None,
|
||||
preferred=None, inner=None, outer=None, normal_limit=None,
|
||||
coverage_limit=None, fragment_gate=True):
|
||||
cx, cy = center or CENTER
|
||||
inner = INNER if inner is None else inner
|
||||
outer = OUTER if outer is None else outer
|
||||
normal_limit = NORMAL_LIMIT_DEG if normal_limit is None else normal_limit
|
||||
coverage_limit = COVERAGE_MIN if coverage_limit is None else coverage_limit
|
||||
bounds = (cx - outer, cx + outer, cy - outer, cy + outer)
|
||||
if scene_data is None:
|
||||
scene_data = RING.read_scene(str(path), 0,
|
||||
HEIGHT_MIN if height_min is None else height_min,
|
||||
HEIGHT_MAX if height_max is None else height_max, bounds)
|
||||
voxel_um, triangles, tiles = scene_data
|
||||
component_count, fragments, below_floor = small_components(triangles, (cx, cy))
|
||||
print(f"[MouthFragmentComponents] mesh={path.name} components={component_count} "
|
||||
f"small_near_mouth={len(fragments)} subthreshold_near_mouth={len(below_floor)} "
|
||||
f"limits=(triangles<={FRAGMENT_MAX_TRIANGLES},"
|
||||
@@ -94,16 +103,18 @@ def audit(path):
|
||||
f"distance<={FRAGMENT_MAX_DISTANCE_M:.1f}m) "
|
||||
f"first={fragments[0] if fragments else None} "
|
||||
f"subthreshold={below_floor[0] if below_floor else None} "
|
||||
f"status={'PASS' if not fragments else 'FAIL'}")
|
||||
f"gate={'on' if fragment_gate else 'diagnostic'} "
|
||||
f"status={'PASS' if not fragments or not fragment_gate else 'FAIL'}")
|
||||
preferred = PREFERRED if preferred is None else preferred
|
||||
cell = max(2.0, 16.0 * voxel_um / 1_000_000.0)
|
||||
buckets = RING.build_buckets(triangles, True, bounds, cell)
|
||||
lines = []
|
||||
count = round((OUTER - INNER) / STEP)
|
||||
count = round((outer - inner) / STEP)
|
||||
for degrees in (135.0, 180.0, 225.0, 270.0, 315.0):
|
||||
angle = math.radians(degrees)
|
||||
lines.append((f"radial-{degrees:.0f}", [
|
||||
(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
|
||||
for radius in (INNER + i * STEP for i in range(count + 1))]))
|
||||
for radius in (inner + i * STEP for i in range(count + 1))]))
|
||||
tangent_count = round(8.0 / STEP)
|
||||
for degrees in (225.0, 315.0):
|
||||
angle = math.radians(degrees)
|
||||
@@ -118,7 +129,7 @@ def audit(path):
|
||||
max_pair = None
|
||||
min_coverage = 1.0
|
||||
for name, points in lines:
|
||||
samples = [RING.sample_surface(buckets, x, y, True, cell, PREFERRED)
|
||||
samples = [RING.sample_surface(buckets, x, y, True, cell, preferred)
|
||||
for x, y in points]
|
||||
coverage = sum(value is not None for value in samples) / len(samples)
|
||||
min_coverage = min(min_coverage, coverage)
|
||||
@@ -136,11 +147,11 @@ def audit(path):
|
||||
max_pair = (name, points[index - 1], prior[0], points[index], current[0])
|
||||
print(f"[MouthFadeLine] mesh={path.name} line={name} coverage={coverage:.3f} "
|
||||
f"max_normal_jump_deg={line_max:.6f}")
|
||||
normal_pass = max_angle <= NORMAL_LIMIT_DEG and min_coverage >= COVERAGE_MIN
|
||||
passed = normal_pass and not fragments
|
||||
normal_pass = max_angle <= normal_limit and min_coverage >= coverage_limit
|
||||
passed = normal_pass and (not fragment_gate or not fragments)
|
||||
print(f"[MouthFadeAudit] mesh={path.name} tiles={tiles} lines={len(lines)} "
|
||||
f"coverage_min={min_coverage:.3f} coverage_limit={COVERAGE_MIN:.2f} "
|
||||
f"max_normal_jump_deg={max_angle:.6f} normal_limit_deg={NORMAL_LIMIT_DEG:.1f} "
|
||||
f"coverage_min={min_coverage:.3f} coverage_limit={coverage_limit:.2f} "
|
||||
f"max_normal_jump_deg={max_angle:.6f} normal_limit_deg={normal_limit:.1f} "
|
||||
f"fragments={len(fragments)} "
|
||||
f"worst_pair={max_pair} status={'PASS' if passed else 'FAIL'}")
|
||||
return passed, max_angle
|
||||
|
||||
Reference in new issue
Block a user