Document where matrix row writes are silently lost

The operator kept the [UnscopedRef] ref this[int] row indexer as is. design.md
and the float2x2/float3x3 indexers now say where m[r][c] = v lands: locals,
array elements, class fields, and ref locals keep the write; a readonly field,
in parameter, property (auto-properties too), or static readonly identity
takes a defensive copy, so the write compiles and is lost. Fix: copy to a
local, write, assign back. Verified by compiling and running each case with
Roslyn against CultMath (no diagnostics; writes land 5, lost 0).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-14 14:18:00 +02:00
commit 6bc6db68d4
5 changed files with 7 additions and 0 deletions

View file

@ -37,6 +37,11 @@ That makes these HLSL rules, not options:
products go through `mul` (`mul(m, v)` treats `v` as a column, `mul(v, m)` as a
row). `*` on matrices would be component-wise in HLSL, so CultMath does not
define it.
- `m[r][c] = v` writes through locals, array elements, fields of classes, and
ref locals. Through a readonly field, an `in` parameter, a property
(auto-properties included), or a static readonly value such as
`float3x3.identity`, C# takes a defensive copy: the write compiles and is
silently lost. Copy the matrix to a local, write to it, and assign it back.
- Scalar/vector arithmetic and comparisons have explicit overloads on both
sides rather than leaning on implicit scalar splat.
- Intrinsics return HLSL's types and edge cases: `sign` returns `int`/`intN`

View file

@ -34,6 +34,7 @@ public struct float2x2 : IEquatable<float2x2>
public float _m10 { readonly get => _row1.x; set => _row1.x = value; }
public float _m11 { readonly get => _row1.y; set => _row1.y = value; }
/// <remarks>Writes through a readonly field, <c>in</c> parameter, property, or <c>identity</c> hit a defensive copy and are lost; write to a local and assign it back.</remarks>
[UnscopedRef]
public ref float2 this[int row]
{

View file

@ -42,6 +42,7 @@ public struct float3x3 : IEquatable<float3x3>
public float _m21 { readonly get => _row2.y; set => _row2.y = value; }
public float _m22 { readonly get => _row2.z; set => _row2.z = value; }
/// <remarks>Writes through a readonly field, <c>in</c> parameter, property, or <c>identity</c> hit a defensive copy and are lost; write to a local and assign it back.</remarks>
[UnscopedRef]
public ref float3 this[int row]
{