Skip to content

test(geometry): count strict directed edge pairs beside the signed balance (#3397) - #3416

Merged
louistrue merged 2 commits into
mainfrom
feat-3397-census-strict-directed-pair
Aug 28, 2026
Merged

test(geometry): count strict directed edge pairs beside the signed balance (#3397)#3416
louistrue merged 2 commits into
mainfrom
feat-3397-census-strict-directed-pair

Conversation

@louistrue

@louistrue louistrue commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Closes #3397.

edge_stats measured watertightness with a SIGNED per-edge balance (+1 forward, -1
reverse, count non-zero). A duplicated face contributes one extra forward and one extra
reverse use of each edge and cancels to zero; so does a 2-forward / 2-reverse seam. Both
total unmatched edges and torn void hosts derive from that count, so a duplicate-face
defect was invisible to the gate in either direction.

The repo already held the stricter reading and said why — touching_operand.rs ("A
net-signed tally is not enough: an edge used twice forward and twice reverse cancels to
zero, so a non-manifold seam would be certified closed"), the bowtie fixture in
processors/boolean/chain_cycle_tests.rs, and #3388's pin checking forward == 1 && reverse == 1 per directed edge. The census used the weaker one.

Option 1 as filed: a SECOND column, not a replacement

strict is added beside open, both computed from the same walk over the same triangles
so they cannot drift. Replacing the signed count outright would change every number in the
golden and surface pre-existing non-manifold hosts as new tears; keeping both is what lets
the two readings be compared per host.

The number the issue was filed to obtain

Over 1170 corpus hosts:

count
strict < signed 0
strict == signed 1054
strict > signed 116
of those, crossing the watertight boundary (open == 0, strict > 0) 7

Both numbers matter and quoting either alone misleads. The two rules disagree on 116
hosts; on 109 of them both already call the host torn and strict simply counts more
unmatched edges, so the signed balance was under-reporting severity without changing the
verdict. The 7 are where the census would have called a host watertight and the strict
rule does not — the population the gate could have been wrong about.

strict < signed on zero hosts is the sanity property: a directed-pair rule can only find
the same defects or more, never fewer, so a single host below would mean one counter is
wrong rather than merely weaker.

The re-bless, verified rather than trusted

Regenerating a golden accepts every difference in it, including ones the change did not
cause. So the manifest was checked, not blessed and hoped for: 1170 data rows before and
after, 9 -> 10 columns, and stripping the new strict column reproduces origin/main's
rows byte-for-byte.
No existing column moved on any host. Re-verified at the final head,
not only when first generated.

HEADER gained a strict: legend describing what it sees that open cannot, so #3401's
byte-for-byte golden assertion stays satisfied.

The blind-spot note on open_is_a_defect_count documented this exact limitation and would
otherwise have been a comment that was true when written and false afterwards; it now says
what is measured and what still is not.

Test-only: three files under rust/geometry/tests/. No changeset, no API surface.

Gates: cargo test -p ifc-lite-geometry --no-fail-fast exit 0,
cargo clippy --workspace --exclude ifc-lite-wasm --all-targets -- -D warnings exit 0.

Summary by CodeRabbit

  • New Features

    • Added strict directed-edge metrics to geometry quality reports and aggregate totals.
    • Census exports now include the strict-edge count for each entry.
    • Diagnostics distinguish signed edge-balance issues from strict directed-pair violations.
  • Bug Fixes

    • Improved detection of doubled faces, holes, and snap-collapsed triangles.
    • Regression checks now flag unexpected increases in strict-edge violations while preserving repair-related exceptions.
  • Tests

    • Expanded coverage for clean meshes, topology defects, serialization, round trips, and golden validations.

…lance (#3397)

`edge_stats` measured watertightness as a SIGNED per-edge balance: an
undirected edge counted when its forward and reverse use counts differed. A
face duplicated along with its opposite-wound twin adds one forward and one
reverse use of each edge and cancels to zero, and so does a 2-forward /
2-reverse seam. `total unmatched edges` and `torn void hosts` both derive from
that count, so a duplicate-face defect was invisible to the census in either
direction. The repo already holds the stricter reading and says why:
`touching_operand.rs` ("A net-signed tally is not enough"), the bowtie fixture
in the boolean chain-cycle tests, and #3353's pin on `forward == 1 &&
reverse == 1`.

Option 1 from the issue: a strict directed-pair count as a SECOND column on
`HostRow`, not a replacement. Both readings come off ONE walk over ONE 1 mm
snapped topology in `edge_stats`, so they cannot drift into two measurements of
two meshes. `open`, `alt` and `pre` keep the signed reading, so the gated defect
population and the `torn` / `torn_solid` / `total unmatched edges` totals are
unmoved and no pre-existing non-manifold host is reclassified into a tear.

The number the issue was filed to get, over the 1170 swept void hosts:

  7    hosts watertight by the SIGNED balance and torn by the STRICT rule
  116  hosts where the two readings disagree at all
  1430 edges the signed balance cannot see (20453 signed, 21883 strict)

A RISE in `strict` gates as a worsened count, so the pair that hides the hazard
(`open` falling while `strict` rises) files as a regression rather than as the
friendly re-tessellation verdict. A FALL is qualified through the same
`fall_note` as a falling `open`, so one arm cannot print "(improved)" beside
the other's "(geometry lost)".

The golden was re-blessed through `IFCLITE_CENSUS_BLESS`. Diffed against a
census run from the parent commit: columns 1-9 are byte-identical on all 1170
hosts, and only the appended column differs. `strict` is written LAST for
exactly that reason, so `cut -f1-9` still reproduces the pre-#3397 row.

The blind-spot note on `open_is_a_defect_count` / `open_is_comparable`
documented this limitation as un-gated. It now says what is measured and what
still is not: `alt` and `pre` stay signed, so a doubled sheet only one
triangulator emits is still invisible to `diverged`, and neither reading sees a
self-intersection or coincidence finer than the 1 mm snap.
…ng it twice

/simplify: the `unit_cube()` + `[0, 3, 2, 0, 2, 3]` doubling was constructed in
two tests, and the comment explaining why those six indices are a doubling
rather than a hole sat only on the first copy. A drifted copy would leave
`a_real_hole_moves_both_readings_and_strict_is_never_below_open` asserting
`strict >= open` over some other mesh and still passing.

Extracted `doubled_face_cube()` next to `unit_cube()` and moved the rationale
onto it. No behaviour change.
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
ifc-lite-dev Ignored Ignored Preview Aug 28, 2026 4:27pm
ifc-lite-viewer-embed Ignored Ignored Aug 28, 2026 4:27pm

@cursor

cursor Bot commented Aug 28, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_cac7a897-2552-47aa-a04d-18d7481bd68b)

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 471e2ea9-0b14-4bc8-85df-b409a36e1797

📥 Commits

Reviewing files that changed from the base of the PR and between 075162d and 79092e0.

⛔ Files ignored due to path filters (1)
  • rust/geometry/tests/manifests/watertightness_census.tsv is excluded by !**/*.tsv
📒 Files selected for processing (2)
  • rust/geometry/tests/census_golden/mod.rs
  • rust/geometry/tests/triangulation_invariance.rs

Included review availability: 0 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.


📝 Walkthrough

Walkthrough

The census now records signed and strict directed-edge violations. Host rows, corpus totals, classification, TSV serialization, golden checks, and topology fixtures validate both readings.

Changes

Strict watertightness census

Layer / File(s) Summary
Edge statistics and topology fixtures
rust/geometry/tests/triangulation_invariance.rs
The topology walk computes signed, strict, and degenerate edge statistics. Fixtures cover duplicated faces, holes, clean cubes, and collapsed triangles.
Census classification and totals
rust/geometry/tests/triangulation_invariance.rs, rust/geometry/tests/census_golden/mod.rs
Host rows and corpus totals store strict counts. Classification gates strict increases and qualifies strict decreases with the existing repair conditions.
TSV contract and exhaustive validation
rust/geometry/tests/census_golden/mod.rs
TSV rows require a tenth strict column. Round-trip, parser-error, invariant, and exhaustive classification tests cover the new metric.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: ⚪ Minimal · up to 79092

This test-only change adds strict directed-edge census coverage and updates verified golden data; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant CensusHost
  participant edge_stats
  participant CensusClassifier
  participant CorpusTotals
  participant GoldenTSV
  CensusHost->>edge_stats: compute open and strict violations
  edge_stats-->>CensusHost: return edge statistics
  CensusHost->>CensusClassifier: provide HostRow
  CensusClassifier->>CorpusTotals: classify host and accumulate totals
  CorpusTotals->>GoldenTSV: render ten-column census row
  GoldenTSV-->>CensusHost: parse and validate strict value
Loading

Poem

A rabbit counts each edge with care
Signed paths and strict pairs meet there
Ten columns roll through golden light
Cubes and holes confirm the rite
Duplicate sheets no longer hide
The census keeps both counts beside

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The changes implement the linked issue's core requirements. They add a strict count beside the signed count, compute both from the topology walk, preserve existing signed measurements, serialize the t… Provide reviewable evidence for rust/geometry/tests/manifests/watertightness_census.tsv, or remove the !**/*.tsv exclusion, to verify the required 1170-row, 10-column golden update and preserved original columns.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding strict directed-edge pair counts beside the existing signed balance in geometry tests.
Out of Scope Changes check ✅ Passed The reviewed changes are test-only and directly support the linked issue. They add strict census measurements, serialization, classification logic, fixtures, and validation without unrelated productio…
Full details: Linked Issues check

Explanation

The changes implement the linked issue's core requirements. They add a strict count beside the signed count, compute both from the topology walk, preserve existing signed measurements, serialize the tenth column, and add regression coverage. The corpus TSV is excluded from review by the !**/*.tsv path filter, so the golden-file update cannot be independently verified.

Full details: Out of Scope Changes check

Explanation

The reviewed changes are test-only and directly support the linked issue. They add strict census measurements, serialization, classification logic, fixtures, and validation without unrelated production API or changeset modifications.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@louistrue

Copy link
Copy Markdown
Collaborator Author

Review evidence, since the hosted bots are dark today.

Same situation as #3415: the CodeRabbit check here is a rate-limit pass, Bugbot is out of Cursor usage, and Vercel Agent Review is out of credit. None of them read this diff, and none of them fail.

CodeRabbit CLI, run locally against this branch's merge-base:

{"type":"complete","status":"review_completed","findings":0,
 "reviewedFiles":["rust/geometry/tests/census_golden/mod.rs",
                   "rust/geometry/tests/manifests/watertightness_census.tsv",
                   "rust/geometry/tests/triangulation_invariance.rs"]}

All three changed files named, 0 findings. A real review rather than a refusal — the CLI exits 0 either way, so naming its subject is the discriminator.

The re-bless is the part worth an independent check before merging, and it was verified rather than trusted: strip the new strict column from the golden and all 1170 data rows come back byte-identical to origin/main. That is what separates "added a column" from "blessed a regression away while adding a column". Re-confirmed at the published head, not only when first generated.

Local gates: cargo test -p ifc-lite-geometry --no-fail-fast exit 0, cargo clippy --workspace --exclude ifc-lite-wasm --all-targets -- -D warnings exit 0.

@github-actions

Copy link
Copy Markdown
Contributor

Viewer benchmark

2 metric(s) exceeded the regression threshold (advisory only, not blocking).

01_Snowdon_Towers_Sample_Structural(1).ifc

Baseline recorded 2026-07-01T20:31:05.538Z on github-actions ubuntu-latest, viewer-benchmark-ci (headless Chrome, SwiftShader ANGLE), production build.

Metric Current Baseline Delta Threshold Status
firstBatchWaitMs 2206ms 2905ms -24.1% +50%
firstVisibleGeometryMs 2879ms 3652ms -21.2% +50%
streamCompleteMs 3597ms 3598ms -0.0% +50%
spatialReadyMs 1692ms 1032ms +64.0% +50%
metadataCompleteMs 2249ms 3063ms -26.6% +50%
totalWallClockMs 3700ms 3700ms 0.0% +50%

AC20-FZK-Haus.ifc

Baseline recorded 2026-07-01T20:30:59.972Z on github-actions ubuntu-latest, viewer-benchmark-ci (headless Chrome, SwiftShader ANGLE), production build.

Metric Current Baseline Delta Threshold Status
firstBatchWaitMs 435ms 1075ms -59.5% +50%
firstVisibleGeometryMs 2955ms 1572ms +88.0% +50%
streamCompleteMs 1502ms 1980ms -24.1% +50%
spatialReadyMs 1250ms 915ms +36.6% +50%
metadataCompleteMs 1349ms 1392ms -3.1% +50%
totalWallClockMs 3000ms 3300ms -9.1% +50%

Refresh the baseline from a CI run: dispatch the Benchmark workflow with record_baseline, download the benchmark-baseline artifact, and commit baseline.json (see tests/benchmark/README.md).

@louistrue
louistrue merged commit 8e1dd17 into main Aug 28, 2026
40 of 41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Census watertightness uses a signed edge balance, so a duplicated face cancels to zero

1 participant