The MessagePack source generator cannot resolve CultRecordRef<T> on its own and
emitted a broken formatter reference; MessagePackKnownFormatter on the open
generic did not change that. An explicit MessagePackFormatter per member is the
same formatter the cache resolver uses, so the wire stays a key string.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The MessagePack source generator emitted a broken formatter reference for the
CultRecordRef<T> reference members. Declaring CultRecordRefFormatter<> a known
formatter makes the generated resolver defer to the cache's own.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
e420410f (D11) refuses a reference member whose CLR shape the cache cannot
walk; many:false needs CultRecordRef<T>. CultGeometryBuildRequest.DomainKey,
CultGeometrySelectedCutManifest.RequestKey and CultGeometryChunkArtifact.CutKey
were strings marked [CultReference], so registering the geometry catalog threw
and 7 geometry tests failed on main. All three are record keys of another
document, so the members become CultRecordRef<T> (the attribute is dropped, the
type declares the reference). The msgpack formatter writes a CultRecordRef as
its key string, so the wire bytes and the fingerprint inputs are unchanged.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
CultMathJson is the JSON twin of CultMathResolver: vectors, quaternion and
Color32 are objects keyed by field name, matrices are arrays of row objects,
rect is {min,max}. Consumers add them with AddCultMathConverters(); CultLib owns
no JSON options. CultCircle and CultSphere serialize and deserialize through
them, with [JsonConstructor] so the struct constructor is used. The Caching
README documents each MessagePack and JSON shape in one table.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GameCult.Geometry no longer carries vector or rectangle values. CultCircle and
CultSphere hold float2/float3, the isosurface and surface-nets extractors take
float3 origin, and the per-call conversion to float3 is gone. Their MessagePack
bytes are unchanged (pinned against the pre-collapse encodings), now produced
by CultMathResolver through the default CultCache options.
This is a breaking change to the GameCult.Geometry public API.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
samples and cellSize were validated but isoValue and origin were not:
iso = NaN or +inf silently returned an empty mesh, and origin = NaN gave
NaN positions. Extend Extract's existing top-of-method validation authority
to reject non-finite isoValue and non-finite origin components with
ArgumentException, matching the existing style, with tests for each
component.
Overflow at extreme magnitudes (cellSize ~3e38, or finite samples whose
interpolation overflows to inf) is a separate, float-domain limit rather
than a validated-input error; documented in the XML doc and provenance
notes, deliberately not range-checked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TryGetOtherEndpoint's bool return was dead control flow: when a mutant
flipped its `return false` guard to `return true`, `other` stayed a clone
of `coord` (only one component would have been incremented on the true
path that never runs for that guard), so the caller compared a sample
against itself and behaved identically either way. That produced the
equivalent survivors at Extract's :92 continue and the guard's :198
return.
Bound the edge loop with the same condition (coord[axis] < cellCounts[axis])
directly, computing `other` inline once the bound holds. One path instead
of two that happened to agree; the helper is gone, and so is the `continue`
that used to depend on it. Behaviour is unchanged: verified with a
before/after run of the existing suite plus a temporary harness comparing
Extract's Positions/Quads/QuadEdges against ca220a5's code on sphere,
non-cubic, open-plane (each axis), checkerboard, and minimal 2x2x2
fixtures - all byte-for-byte identical.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The provenance doc still described the deleted geometric winding rule
(diagonal cross product must have a positive dot product with the
inside->outside direction) as the spec. That rule was replaced by the
inside-flag times per-axis handedness table (OrientationSign: +1, -1, +1;
axis 1's perpendicular pair (X, Z) is left-handed) when the geometric
fallback was deleted. State that as the actual rule, with the
outward-cross-product property following only on non-degenerate quads.
Also record that non-finite isoValue/origin are rejected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
F1: Extract's locals (position, v0..v3) were assigned through pattern-match
and short-circuited out-var chains whose definite-assignment analysis breaks
under a single mutated operator, so Stryker's Safe Mode dropped all 98
mutants in Extract as compile errors. Restructured to plain null checks and
a for-loop over a uint[4] so every local is unconditionally assigned; no
mutation of a boolean operator can make it fail to compile.
F3: deleted the `<= 1e-20f` fallback in Interpolate. A straddling edge always
has delta > 0 (.NET preserves subnormals), so the guard only ever replaced a
correct interpolated value with a wrong 0.5 midpoint.
F4: deleted the per-quad geometric cross-product winding test. Orientation
now comes only from the topological inside/outside flag plus a fixed
per-axis handedness sign (OrientationSign; axis 1's (X, Z) perpendicular
pairing is the one left-handed case, X x Z == -Y). This is now determined
purely by which endpoint of the crossing edge is inside and the axis, never
by quad geometry, so degenerate (zero-area) quads orient consistently with
their neighbours instead of falling back to an unstable dot-product sign.
F7: Extract now rejects any non-finite sample (NaN or +-Infinity) with
ArgumentException, in the same style as the existing shape/cellSize checks.
F8: collapsed three duplicated (edge, axis) mappings - TryGetOtherEndpoint's
per-axis switch, the three Axis*EdgeStarts tables plus the axis==0?1:0
ternaries in AccumulateAxisEdges, and TryGetQuadCells' three axis-specific
corner blocks - into one PerpAxes/PerimeterLoop table pair, used by all
three. Replaced the three duplicated `<= isoValue` inside tests with one
IsInside predicate. Deleted the per-axis trailing `InRange(x, cellsX)` /
`InRange(y, cellsY)` / `InRange(z, cellsZ)` checks (provably always true,
since TryGetOtherEndpoint already guarantees the edge's own-axis coordinate
indexes a valid cell) and the explicit TryGetValue+throw invariant guard
(the four cells around a crossing edge are proven to always have a vertex;
a direct dictionary index now fails loudly via KeyNotFoundException if that
invariant is ever actually violated, with no reachable branch to maintain).
F6: deleted CultGeometryGridEdge's <, >, <=, >= operators (CultGeometryQuadMesh.cs).
Nothing in production or tests used them; ordering goes through CompareTo.
!= is kept only because C# requires it whenever == is defined (CS0216).
Builds clean; does not touch CultGeometryIsoSurface.cs, CultGeometryDocuments.cs,
or ISOSURFACE-PROVENANCE.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements CultGeometrySurfaceNets.Extract (welded CultGeometryQuadMesh, one
quad per interior crossing grid edge, outward-oriented winding) and
CultGeometryQuadNormals.FaceWeighted (unnormalized diagonal-cross-product
area weighting, matching CultGeometryIsoSurface's sign convention and
degenerate-normal rule). Fresh implementation per SURFACE-NETS-PROVENANCE.md;
no code or tables copied from the cited literature.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dxc 1.9.0.5402 lowers float2/3/4 normalize(x) to x * Rsqrt(Dot(x, x)),
and DXIL.rst defines Rsqrt as 1 / sqrt(src). A zero vector normalizes
to NaN and a NaN component poisons every component. CultMath divided
by max(length, 1e-20), which returned 0 for a zero vector and a huge
finite component beside a NaN one.
- math.normalize(float2/3/4) is now value * (1 / sqrt(dot(value, value))).
- The HLSL mirror drops cultmath_normalize and its epsilon constant: it
now equals the normalize intrinsic, and nothing consumed it.
- HlslSemanticsTests pins zero and NaN normalize, the rsqrt bit pattern,
and zero length/distance. design.md states the rule and notes that
DXIL.rst's Rsqrt special-value table is a copy of Round's.
- GameCult.Geometry's iso-surface gave degenerate triangles (an inside
vertex on the isovalue) a zero normal through the old epsilon. It now
guards them explicitly, with a test.
- quaternion normalize keeps its identity fallback: quaternion is not
an HLSL type.
- Bump to 0.2.2 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>