Skip to content

Shadow deformation policy - #130

Merged
nnewson merged 2 commits into
mainfrom
shadow-deformation-policy
Jul 31, 2026
Merged

Shadow deformation policy#130
nnewson merged 2 commits into
mainfrom
shadow-deformation-policy

Conversation

@nnewson

@nnewson nnewson commented Jul 31, 2026

Copy link
Copy Markdown
Owner

No description provided.

nnewson added 2 commits July 31, 2026 12:27
…asured on a pose they are never in

SH-03 threaded per-view shadow LOD through the renderer and, in doing
so, applied it to every caster: object.cpp built each request with the
frame's LOD toggle and nothing else. The simplifier measures deviation
on the mesh as authored — bind pose, base weights, the vertex buffer at
build time — so for a skinned caster that number describes geometry that
is never rasterised. This is not a loose estimate. A joint rotation can
carry a vertex arbitrarily far from where its rest-pose deviation was
taken, so the error is unbounded, and SH-02 was careful to call the
metric an estimate precisely because it could not cover this case.

It was live rather than theoretical. BrainStem logged a shadow-level
transition on a skinned caster within seconds of starting, and the
shadow-LOD demo's skinned self-shadow caster was drawing 184 of its 1248
triangles on the strength of that claim.

ShadowGeometryRequest now carries a ShadowCasterDeformation
classification. Deliberately not expressed as lodEnabled = false, which
would have reported LodDisabled and conflated a user's toggle with a
safety fallback — the panel would then answer "why is this caster at
full detail?" with somebody else's reason. It defaults to Deformable on
the same principle as worldScale's NaN: a producer that forgets the
field must not receive the optimistic answer. That default immediately
caught every existing resolver fixture, which is the intended behaviour
on its first day.

The resolver answers with DeformableFallback: the whole mesh, and an
INFINITE projected error rather than zero, because zero would rank a
deformable caster as the most accurate in the frame. Precedence runs
InvalidCaster, LodDisabled, DeformableFallback, SingleLevel, then
selection — a producer bug outranks a property of well-formed geometry,
and a global switch is the operative fact about every caster rather than
a deformable one being more disabled than the rest. Deformation is
checked before chain length so a single-level deformable caster still
reports why it may not select. No hysteresis history is staged, which
the Selected-only staging rule already guaranteed and a test now pins.

Classification covers all three carriers that deform a mesh after
measurement: skinned or morph-weighted instances, morph-CAPABLE geometry
independent of its current weights, and storage-vertex geometry a
compute pass rewrites. Weights are this frame's value, not a property —
classifying by them would swap a caster's error model mid-animation.
Cloth is storage-vertex and single-level, so it was safe by accident;
classifying it explicitly stops that accident being load-bearing the day
storage-vertex geometry gains an LOD chain. It lives in its own header
rather than as an Object member so classification and the resolver's
response to it are tested separately, and neither is proven only by the
other.

Object::shadowGeometry is removed rather than documented as unsafe. It
was unused and it was a public route around every rule above:
substituting a proxy silently substitutes its topology, its deformation
carriers and its bounds, with nothing checking that any of them match. A
validated proxy API — deformation compatibility, an explicit morph
contract, proxy-derived bounds, enforced at load time — is the remaining
half of SH-04, and no route in beats an unchecked route in.

The calibration was re-run rather than assumed to survive, since
ShadowLodDemo carries a skin and a morph primitive by design. Cost moved
as expected: the self-shadow caster went from 184/1248 triangles to
1248/1248, and the cascade group from 59.9% to 68.2% of full detail at
budget 1. The error column did not move at all — not one figure — and
the reason is a limitation of the metric rather than the fix being
inert: the panel reports 13 deformable resolutions scene-wide but 0 in
cascade 0, and --debug-shadow only visualises primary directional
visibility, so the metric never saw the self family these casters live
in. The pre-registered 0.1% threshold was re-applied unchanged and still
selects budget 1; budget 2 becoming eligible was a live possibility
worth checking, and it did not happen. Reversals remain 0.00 at every
ratio, so the dead band stays disabled.

Also corrected in this change: five contracts that still described
pre-SH-03 behaviour (LodDisabled's switch, the request's mirrored flag,
--no-lod seeding two switches, the tint flags and the fixed
camera-derived defect in onboarding), and the dead shadow-proxy
scaffolding the removed setter left behind — the binding's
shadowGeometry field and its branches are gone, while shadowGeneration
stays in the hysteresis key as reserved for the validated proxy API, now
covered by a test since nothing else calls it.
@nnewson
nnewson merged commit 1fd7569 into main Jul 31, 2026
4 checks passed
@nnewson
nnewson deleted the shadow-deformation-policy branch July 31, 2026 12:49
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.

1 participant