AI-Friendly Geometry Validation

This document describes efficient methods for AI agents to validate 3D geometry results without relying on visual rendering.

The Problem

Humans validate geometry by looking at rendered images. AI agents cannot efficiently:

The Solution: Use HiScene JSON

The best approach is to export HiScene - FCS's internal scene format. See 09-HISCENE-FORMAT.md for complete documentation.

# Export HiScene from fli.exe
fli.exe model.fcs "Main" --t 3JS --o output.hiscene.json

HiScene provides:

Alternative: Geometric Fingerprints

For simpler validation without full scene export, extract numeric properties that characterize the geometry:

1. Topology Counts (Integers)

# Query these via fli.exe
validation := {
    vertex_count := assembly.Vertices.Count,
    curve_count := assembly.Curves.Count,
    area_count := assembly.Areas.Count,
    volume_count := assembly.Volumes.Count
}

Test assertion example:

{
    "expected_vertices": 8,
    "expected_curves": 12,
    "expected_areas": 6
}

2. Bounding Box (6 numbers)

The axis-aligned bounding box captures overall shape extents:

bbox := Fcs.Geometry.BoundingBox(assembly)
# Access: bbox.Min.X, bbox.Min.Y, bbox.Min.Z
#         bbox.Max.X, bbox.Max.Y, bbox.Max.Z

Fingerprint:

{
    "bbox_min": [0, 0, 0],
    "bbox_max": [10, 5, 3],
    "tolerance": 0.001
}

3. Geometric Invariants (Single numbers)

Property Description FCS Query
Total curve length Sum of all edge lengths curves.Select(c => c.Geometry.Length).Sum
Total area Sum of all surface areas areas.Select(a => a.Geometry.Area).Sum
Centroid Center of mass assembly.Centroid
Diagonal Bounding box diagonal Sqrt((bbox.Max.X-bbox.Min.X)^2 + ...)

4. Vertex Coordinate Dump (Full geometry)

For exact validation, dump all vertex coordinates as text:

# Create parseable output
vertex_dump := vertices.Select(v => [v.X, v.Y, v.Z])

Expected format:

[[0,0,0], [10,0,0], [10,5,0], [0,5,0], ...]

5. Connectivity Matrix (Topology)

For structural models, validate element connectivity:

# Which vertices connect to which curves
connectivity := curves.Select(c => [c.StartVertex.Id, c.EndVertex.Id])

Validation Test Format

Simple Test (Numeric Invariants)

{
    "id": "test-007-box-geometry",
    "model": "simple_box.fcs",
    "validation": {
        "type": "invariants",
        "checks": [
            { "expr": "vertices.Count", "expected": 8 },
            { "expr": "curves.Count", "expected": 12 },
            { "expr": "areas.Count", "expected": 6 },
            { "expr": "bbox.Max.X - bbox.Min.X", "expected": 10, "tolerance": 0.001 }
        ]
    }
}

Full Geometry Test (Hash-based)

{
    "id": "test-008-complex-model",
    "model": "cooling_tower.fcs",
    "validation": {
        "type": "geometry-hash",
        "expression": "Fcs.Geometry.Hash(assembly)",
        "expected_hash": "a7f3b2c1d4e5..."
    }
}

Comparison: Visual vs Numeric Validation

Aspect Visual (PNG) Numeric (Invariants)
File size ~50KB-500KB ~100 bytes
Parse time Requires vision model Direct comparison
Precision Pixel-level artifacts Exact to machine epsilon
3D depth Lost in 2D projection Fully preserved
Rotation invariance ✗ (view-dependent) ✓ (intrinsic properties)
AI-friendly

Implementation Pattern

1. Define Expected Invariants

# In test-model-expected.fcs
expected := {
    vertex_count := 4,
    bbox_size := [10, 5, 0],
    total_length := 30,
    centroid := [5, 2.5, 0]
}

2. Query Actual Model

fli.exe model.fcs "vertices.Count"
fli.exe model.fcs "[bbox.Max.X - bbox.Min.X, bbox.Max.Y - bbox.Min.Y, bbox.Max.Z - bbox.Min.Z]"
fli.exe model.fcs "curves.Select(c => c.Geometry.Length).Sum"

3. Compare in Test Runner

$actual_count = & $fli $model "vertices.Count" | Select-Object -Last 1
$expected_count = 4
if ($actual_count -ne $expected_count) {
    Write-Host "FAIL: vertex count $actual_count != $expected_count"
}

Advanced: Projection Fingerprints

If you truly need 2D projection validation, use coordinate projections not images:

# Project vertices to XY plane and compute basic stats
xy_projection := vertices.Select(v => [v.X, v.Y])
xy_centroid := [xy_projection.Select(p => p[0]).Average, xy_projection.Select(p => p[1]).Average]
xy_spread := xy_projection.Select(p => Sqrt(p[0]^2 + p[1]^2)).Max

This gives you:

Summary

For AI agents, numbers beat pixels. Use:

  1. HiScene JSON - Complete 3D scene with exact geometry (best! see 09-HISCENE-FORMAT.md)
  2. Topology counts - vertex/curve/area counts
  3. Bounding box - 6 numbers capture overall shape
  4. Geometric invariants - total length, area, centroid
  5. Coordinate dumps - full vertex list for exact match
  6. Connectivity - topology relationships

This approach is: