From 6bc6db68d454919ce704e23036099fd0cf9b8073 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 14 Sep 2026 14:18:00 +0200 Subject: [PATCH] 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 --- packages/cultmath/docs/design.md | 5 +++++ packages/cultmath/src/CultMath/float2x2.cs | 1 + packages/cultmath/src/CultMath/float3x3.cs | 1 + .../Runtime/Plugins/CultMath.dll | Bin 297472 -> 297472 bytes .../Runtime/Plugins/CultMath.pdb | Bin 141076 -> 141076 bytes 5 files changed, 7 insertions(+) diff --git a/packages/cultmath/docs/design.md b/packages/cultmath/docs/design.md index 863b5efe..ee209fd4 100644 --- a/packages/cultmath/docs/design.md +++ b/packages/cultmath/docs/design.md @@ -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` diff --git a/packages/cultmath/src/CultMath/float2x2.cs b/packages/cultmath/src/CultMath/float2x2.cs index 9d5a2e54..bd8673ae 100644 --- a/packages/cultmath/src/CultMath/float2x2.cs +++ b/packages/cultmath/src/CultMath/float2x2.cs @@ -34,6 +34,7 @@ public struct float2x2 : IEquatable public float _m10 { readonly get => _row1.x; set => _row1.x = value; } public float _m11 { readonly get => _row1.y; set => _row1.y = value; } + /// Writes through a readonly field, in parameter, property, or identity hit a defensive copy and are lost; write to a local and assign it back. [UnscopedRef] public ref float2 this[int row] { diff --git a/packages/cultmath/src/CultMath/float3x3.cs b/packages/cultmath/src/CultMath/float3x3.cs index 4c052a50..d7aad21c 100644 --- a/packages/cultmath/src/CultMath/float3x3.cs +++ b/packages/cultmath/src/CultMath/float3x3.cs @@ -42,6 +42,7 @@ public struct float3x3 : IEquatable public float _m21 { readonly get => _row2.y; set => _row2.y = value; } public float _m22 { readonly get => _row2.z; set => _row2.z = value; } + /// Writes through a readonly field, in parameter, property, or identity hit a defensive copy and are lost; write to a local and assign it back. [UnscopedRef] public ref float3 this[int row] { diff --git a/packages/cultmath/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll b/packages/cultmath/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll index 1b4d8b07aa217b59726799c5ab9d5767bf66408e..28e9c939caed3f388143698250fd014528d76745 100644 GIT binary patch delta 256 zcmZpeBGfQNXhH{z$)lZH8hcuM7+ZUoT6>tc_OR&W2%O9Hy7y0KyW74{8PPXqWKV5B zk;8IFK*QWTHPtLF)x^Rg(a^xe#4_2)I4#-0)X*X&$->OgJk`WB(J(pLFm3ypS{5Eg z7KUw%_om;eV+jz@Qcgct7IM>Zi(>dSTehl=({1WmLKUE*A5Ea5K=pAh^H%KRSm^zK z^W-CX+c(y;yl3?{XE0|-1wu21G$3ihU;%`Q42BE_K(+~kB||cU5rZ*ME*U6d3KX|s fNMT35o7@Xb+1q# delta 256 zcmZpeBGfQNXhH`|x2x-w#-7$5#?~ID)*j}qJuEso0@=%wA8;?LbX`+0V`1zcUd8Pv za#-#NXc!tK86_K=TASfQmOc=3zJp8FCD&*U=tPq(RO2~~iKTKs{E0@cShOw;-9IrXmm zC6Ob}+c(y;yl3?{WH4YzVlZMzW-w$hWk>_EEf`W6EE&vzFa-!Lf!Kf{k--?K#stVt h1G1BVat1)YIZ(s|D3b~lH3jNVV@TcpzLiCg1pwmLQ|bT! diff --git a/packages/cultmath/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb b/packages/cultmath/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb index 4745e11674c90139d756b7d7a9ed85cd3f20f891..c2dc244653945f45fb3a7e9b8730ed57e6fa88f2 100644 GIT binary patch delta 857 zcmbPoj$_I>jtMm)TFU9?%0g~BZcz-sX3JK!kzpI-y~YV!Col>|DlE&7u>06{Q8`eWB%xzcPKa}{z;%UamPqXek zy>mp~{`;$`QH#9aZ`X}toXS%F`=ON9e76%Zb^lI$`cd`XD2aPvh~blYT@@DucWrfh zTG4n_o0WlK?UeIbw;cqIy>Hxe_(5D$ly}*qBY8qxE4?l?@v8b=c7B<-Maq+VwXC7F z*9GOTrk_$=y|Ul@W1L#W&u#tt?fdAoH1odW)w1pEg*@UnH2S$esjQf?_hs|l9re1o z42LBTGPB*_P{?I;oEdw?)BMvCm-qmN)C=!Y@?;%bbtQ6`8o#!=p3qnl@@Q`=;}=Pr zi02Bb1u`Nz`xa(;`rk8axNvOG_LlA+{N?w`v-tRDCDby$Hq!`wzWelxwjy^o-QS=6 zez7_)&?bC$mP-2F~}&-Blh z7c>1fAFXFvKKbUGt(I2j&OS-mb>h;|mnD9ih37~apKZTgCvr||b8PQA!>|=k3y!S~ zb^q^r!};GXzEc5(%Pq6{!Xj_^KXO~25VGppL!CbE)v1%++E)K~9^A1zGxPGj4fSuY z-?sPvuD|A5zVvIOA39;04|~?Jba(A(DwJ@#GWRzhqd21=X{ja_ z7Kw%iCMK52M#gE$2BwA6(9V3YovH=K>_EYWxy9SPjx(N$ F2LPbuUGe|` delta 857 zcmbPoj$_I>jtMm)6IN*H2wwc+vFEjXx@NCj=ZUiaws?l6u| z=ZriT^)?)i+_S8C_1@~u*UoAB9;t#^(edExzgWo|}J~TvtKU+A52n z?ufr3ep6o@c(&&DcHJn(sVwz-g{J1#-7CJnhsogPYKOhEIs8xC*4%y;^R!QEngr*! z#f?|BSs57CPPv^WZ79(8p1HF9;NHy4y}Qd~wp#{xY**pE5%9=lug2aPQ`5qk)^6!Z zlm0gGmsIVh4YRM^ux)T&%iZXY|>hsquQhW>4?UX`+NuM?$k?3 zJF@x~^b1~N(y(%vAP{rO`}(Il3)X8iu}t`vk}2!hE31*C-1t??V6u?Li3jG=5*M;} zY(MAd_`peI!_!%6ZE|~aS(a`6nZWt{W&FE6@2|+nZMpc1D=b!I)%k7HPx4Ofn{wm9 z`Kp)VJriFYDqUXBHK}`kb>#}_FLzrxZZ8t8s<+#9Xx67|Ctn=BK38(zmeA8T+|51A zpKvJKvR*04(N>n8Ab&s6v#G${j_+#VLHj(mzTJ!O2Z!X$zMnV4+R6TzK-P)*j=f0= zQaK*Zk`i`0VcmsO&IuY+`uf;hvA=ou+a;@A%6+%DMH=~TQn@W#{Py&LuM^@+{y*3| zC*^2}Z{O+qN4#^-Jdc|Hf~S9(acq*|`DN?VH!ExPt3MTf^>Jcz-qkeIU&kUA&A2PM zJxu1Wcq0GL*VZ13AA}W~DMoSLsZBDkVpUeYbmX$h+BGw_d4#OE9Ph9Ac6qBV+u?^> z<8%Mn{N2AHW^eY|%O7U04UjX5H3~DWWPECHY>EAM21ap4LB{F(5*WQT3=NWuk_}DM zk}Og!&CF6#EK>{;jg3rF(h`%*42;Z8OpH_0Obydgw;LuhevGuxkz{8OWMW`vFlJ&B z5Mp4Ew&Y}IkOm1TGBL0*Fff?enXm~nFsRvEfp~Hb#vm%jaeLHmMqh3(6PR`sGwfkz z*fTMJ4RuEs@JAA`7G+=%kDmVV7^AgZHYdpV;9L_nF`%7!U^`XwjoE>M4RedOdmU#y G6%PPM)K?M!