docs/camera-occlusion.md
Camera measured — exploration freedom, not a defect
Third result under renderer-as-laboratory. The camera differs from fog and light because the viewer controls it, so the measurement had to separate two things that look identical in a single frame.
tests/camera-occlusion.test.ts.
The task, defined before measuring
Can a user see that a given building exists?
- Renderer claim failure — information that no reachable pose reveals.
- Exploration freedom — information hidden now, recoverable by moving.
A building is reachable if at least one pose inside the control's own limits
(minDistance 18, maxDistance 520, maxPolarAngle π/2.15) leaves it
unoccluded. Occlusion is computed in closed form — perspective projection of box
silhouettes, nearest-first — so no pixels are needed.
Result
18 buildings · 2 occluded at the default vantage (11.1%)
216 reachable poses sampled
unreachable from every pose: 0
Every building is reachable. The camera is exploration freedom, and removing or constraining it would remove freedom rather than a fault.
The 11.1% is a measured cost of the initial framing, which is a convention (worldscene-freedoms.md) — a number attached to a declared choice, not a defect.
The height bias exists and is weak
mean height — hidden 5.0, shown 5.2
Tall buildings do hide short ones, and height carries code-size, so the estate preferentially conceals small things. The mechanism is real; at this scale the effect is marginal. Recorded, not repaired, because the reachability result shows all of it is recoverable.
Classification, on the two axes
| Freedom | Semantic ownership | Perceptual effect |
|---|---|---|
| initial camera pose | conventional | preserves — 11.1% concealed, all recoverable |
| interactive camera | arbitrary | preserves — measured, 0 unreachable |
| distance fog | derived (was arbitrary) | distorted — 36.3%, repaired to 0% |
| key light | arbitrary | preserves — 0 inversions |
| fill light | arbitrary | preserves — contrast only, −5.6% |
| material response | arbitrary | preserves — specular 116× too small |
The camera is the case the two-axis scheme was needed for: arbitrary in ownership, harmless in effect. A single-axis scheme would have flagged it as an unowned claim and invited a repair that removed navigation.
A second finding: the fixture was degenerate
The first run reported zero occlusion, and the instrument guard refused it. Diagnosis:
atScale(10): extent x 0..990, z 0..0 heights 15.8..15.9 footprints 8
Every building on a line, at identical heights. The synthetic fixture cannot
produce occlusion at all. The measurement was moved to deriveStructure, which
is what the product actually renders — rings, slots, varying heights.
The guard is the reason this was caught rather than published. "No occlusion detected" is exactly the kind of clean-looking result that would have gone unquestioned.
And it forced a re-examination of the fog result
The fog measurement used the same degenerate fixture, so I checked whether 36.3% was an artifact of it:
Pearson r(depth, activity) = 0.080
as generated: 36.3% inverted
activity shuffled: 31.4% inverted
Depth and activity are essentially uncorrelated, and breaking any residual correlation leaves the same order of magnitude. The fog finding stands — it depends on depth spread and activity variance, both of which are real in that fixture, and not on the 2-D arrangement, which is not.
The real layout could not test it: with emptyFacts() there is only one
distinct activity value, so there is nothing to invert. That is an unreachable
state, not a refutation.
Standing
| Measurable defect exposed? | no — the camera is not one |
| Measurable improvement? | no changes made, which is the result |
| Freedom removed without evidence? | no |
| Prior finding re-verified? | yes — fog survives a fixture challenge |
What is still not established
- 216 poses is a sample, at 15° azimuth × 3 elevations × 3 distances. Occlusion could persist in a gap between samples, though a building hidden across a 15° sweep and three distances would be unusual.
- Occlusion is centre-point only. A building 90% covered counts as visible. The test answers "can a user see it exists", not "can a user read its height" — a stricter task would give a different number.
- FOV is declared, not read.
50°is r3f's default and is not set inWorldScene.tsx; if a parent Canvas sets it, this measurement drifts. - The estate is 18 buildings from 9 synthetic repos. A denser real estate would occlude more, and reachability is the claim that would need re-testing.
215 → 219 tests, tsc clean.