AI3D-388 · branch t3code/implement-fence-detection · 2026-09-08 · 613 tests green · channel still default-off · off-state byte-identical to main
Where we are. Recall on the training split is essentially solved: an exhaustive audit of all 64 train segments — overview renders, a Hough screen, road-rectified RGB strips and a raw-point scan of every .npz — found no unexplained continuous thin structure within ±48 m of the road in the first 32 segments, and exactly one likely missed fence in the second 32 (at 46 m, outside the search corridor).
What is left is precision and taxonomy. Every scalar shape cue I have added now overlaps between real fences and hard-edged crop rows. The remaining decisions are category judgements — is a maize strip's edge, a 2.4 m panel wall, a rest-area railing or an overgrown mesh line a “fence” under the schema? — and I cannot settle them from the point cloud alone.
What the three passes changed
Pass
Change
Motivation
4
ragged_crest and wall_like gates; flank metrics (hard mass beside the line at height); structure_like shipped as a measured no-op
The pass-3 survivors that were not fences were roofs, solar-panel rows, shelters and an abutment — all of them mass at height beside the fitted line
4b
Thin-sheet exemption in the vegetation gate (width_m ≤ 0.60 m); count-weighted vertex placement; corridor 35 → 45 m behind a new outside_corridor seed gate
Two 66 m and 103 m mesh fences were being deleted as “green”; a fence's fitted line drifted ~1 m off the object; 4 of 7 fences were clipped by the corridor
5
Ridge fit (keep only the densest lateral slice per metre) and the sheet-concentration metric, measured with the fit switched on
Clusters were being polluted by the grass and signage next to the fence
6
crest_flat_frac replaces the p90−p10 crest spread as the gate; post-cadence metric reusing the guardrail channel's comb estimator; ridge fit on by default
A single sign post inside a run moved the old spread metric enough to delete a real fence
What worked
1 · The crest-share metric recovered the post-and-rail fence
The old crest metric (p90 − p10 of the per-station top profile) was moved by any single tall object standing inside the run. In segment 048 a 3 m sign post did exactly that and the fence was rejected. The replacement asks a different question — what share of the stations sit at one height? — and is unmoved by one outlier station: this fence reads 0.88 where the floor is 0.65.
Segment 048 candidate 8 — the post-and-rail fence RECOVERED by pass 6 (ridge fit + crest-share gate). Three rails and their posts are visible in the side view (bottom right). Pass 4 rejected this object.
2 · The ridge fit and the wider corridor lengthened real fences
Keeping only the densest lateral slice per metre stopped the fitted line drifting into the grass beside the fence: on segment 048 the fitted width fell from 1.29 m to 0.48 m. Together with the 45 m corridor (seeded inside 35 m, so nothing new can be proposed that does not start inside the calibrated band), the val 015 fence grew from 28.6 m to 43.8 m and the 092-area run from 34 m to 48.6 m.
Segment 128 candidate 1 — a 66 m mesh fence, accepted. This is what the channel is meant to find: a single-cell-wide spike in the cross-section, flat crest, posts in the side view.
What did not work
3 · Neither new cue separates a fence from a crop edge
This is the central negative result. A maize strip cut by a machine has a dead-flat top and — because the beam cannot see past its first half-metre — its upper mass is concentrated in one cell column, exactly like a mesh sheet. The scatter shows crop edges (green) sitting inside the fence cloud (red) on both axes, and the two fences that fall left of the crest floor.
Pass 6 metric space: crest flatness (x) vs sheet concentration (y) over the labelled candidates. Crop edges (green) sit inside the fence cloud (red); the two fences left of the dashed line are the ones the crest gate now deletes.Segment 092 candidate 2 — the maize strip, still ACCEPTED. The cross-section (top right) shows a 6 m deep block of 2-3-return points with a dead-flat 1.9 m top; the fitted line sits on its road-facing edge.
4 · The post-cadence metric is degenerate as implemented
Reusing the guardrail comb estimator on the fence's own station histogram returns a ~2.0 m period for almost every candidate, fence or not, with scores in a single band (0.14–0.48). At 0.25 m bins the comb is locking onto the scan pattern, not onto posts. It is shipped as a measured metric with no gate; it needs a different formulation (post detection in the low band, as the guardrail channel does it) before it can discriminate.
5 · Two real fences are now lost to gates written for other objects
The 1.20 m top-height floor exists to keep guardrails out of the channel, but it is applied everywhere — so a 40 m field fence 27 m from the road, whose top is 1.08 m, is rejected as “rail-like”. A guardrail never stands 27 m out; the floor should relax with distance from the carriageway edge.
Segment 122 candidate 1 — a 40 m fence 27 m out, REJECTED as rail_like_top because its top is 1.08 m and the floor is 1.20 m. The floor exists to keep guardrails out, but a guardrail never stands 27 m from the edge.
The second loss is defensible: an overgrown line whose crest share is 0.56 because most of what the scanner sees there is undergrowth.
Segment 110 candidate 5 — a fence buried in undergrowth, REJECTED by the crest gate (crest share 0.56). Defensible: what the scanner sees here is mostly foliage.
And the ridge fit has a cost of its own — on segment 124 it merged a fence with the vegetation growing through it, and the merged object died on tall_mass.
Segment 124 candidate 1 — an overgrown fence, REJECTED by tall_mass after the ridge fit merged it with the vegetation growing through it.
Numbers
Pass 6 over the 25 segments that carry every labelled fence and every known false positive: 312 candidates → 28 accepted (pass 4: 180 → 20). Rejections, in gate order: tall_mass 129, inside_carriageway 47, guardrail_overlap 40, outside_corridor 32, ragged_crest 20, rail_like_top 6, wall_overlap 4, vegetation 3, wall_like 3.
On the labelled subset: 14 of 17 fence rows survive, but so do 6 crop edges, 2 structures and 2 unclear objects — a precision of roughly 58 % on the hardest segments in the split. Zero guardrails have ever been stolen by the channel.
Code health
A GPT-5.6 review of the whole channel found three contract violations that were real and are fixed: float32 evidence sums made per-cell station means depend on chunk order; the structure_like aggregates combined occupancy from one side of the line with mass ratio from the other (on segment 129, 5 of 13 candidates were reporting a mixed pair); and profiles built from one or two cells were being treated as trusted evidence. Also fixed: the top-down rerender silently dropped fences, zero-valued cell sizes validated and then divided by zero, per-record columns were held whole instead of per chunk, and the analysis tools ignored the newer label format.
What I would do in the next pass
Make the top-height floor distance-aware.rail_like_top should scale with edge_distance_med_m: a 1.0 m line 27 m out is a fence, a 1.0 m line 4 m out is a rail. This recovers a 40 m fence at no measured cost.
Attack the crop edge with penetration, not shape. The one thing a crop row does that a fence cannot: it returns mass behind its face at the same height, just attenuated. A “what fraction of the upper mass lies 0.5–2 m behind the fitted line, weighted by multi-return” metric is the natural next cue, and unlike the flank metric it must count soft (multi-return) mass, not hard.
Use intensity. The channel does not read it at all today, yet galvanised mesh and maize leaves are far apart in it, and the plumbing already exists for the guardrail mask.
Real post detection in the low band under the fitted line, replacing the degenerate cadence score.
Cross-segment stitching — 4 of 7 fences end exactly on a segment division plane. This one is architectural: the per-segment output contract has no place to put it.
Where I am blocked without you
Three questions are category judgements, not measurements, and every threshold I choose next depends on the answers:
Is the edge of a crop field ever a fence? Today it is my dominant false positive. If a maize strip's edge must never be reported, I need a cue that fails on it — and the two obvious geometric ones do not. If a hard boundary of that kind is acceptable output, the gate set is already close.
What counts as a Fence in the schema? A 2.4 m solid panel wall, a rest-area double-rail railing, a post-and-rail path fence and a wire mesh line are four different objects that all pass the same gates. The HBW class boundary between Fence §7.6, a Geländer and a low noise wall decides whether three of my current “false positives” are actually correct.
How much overgrowth still counts? Fences buried in undergrowth (crest share 0.5–0.7) are exactly where my judges disagree with each other.
Two of the three need a human eye on the same renders. If you label a few dozen candidates across those categories — or just answer the three questions — I can turn the answers into gates immediately.