Eureka target for giving cultmesh-ts a QUIC realtime provider and consumer,
byte-compatible with GameCult.Mesh.Quic and the Unity native MsQuic connector.
First consumer: the StreamPixels service publishing to its Unity overlay.
Operator rulings 2026-09-16: overlay character, subathon window and scene
intents travel as QUIC state frames and the costume catalog as an HTTPS
content artifact; shared TypeScript route verification moves to a
browser-safe module; Windows x64 and Linux x64 only; datagrams out of scope.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corrects the 1.0.58/1.3.0 release. Rebuilds the tracked plugins with
scripts/build-unity-package.ps1 -UpdateTemplate so they carry "" as the
unset CultRecordRef wire form in every position (nil still reads as unset)
and the edit-start Record snapshot. The native QUIC connector changes once
to its /Brepro form; later rebuilds reproduce it byte for byte. The Studio
package moves to 1.3.1 and depends on cultlib 1.0.59.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b85a828 changed x86_64/gamecult_mesh_quic_native.dll with no native
source change. The two copies differ in six bytes: the PE header
TimeDateStamp and the debug directory timestamp. The MSVC link stamps the
build time, so every -UpdateTemplate run rewrote the committed DLL.
/Brepro on compile and link replaces the timestamps with content hashes.
Two clean builds from an emptied build root now produce identical bytes,
and the DLL embeds no build path. The committed DLL changes once to the
reproducible form; after that the release byte check covers it like the
managed plugins.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BeginEdit cloned Document eagerly but Record lazily from record.Document,
so a cache change in place before Record's first read made the two copies
come from different states. BeginEdit now serializes the document once and
deserializes Document from those bytes; Record deserializes the same bytes
on first read. Record is the state the edit began from, and costs one
deserialize only when a drawer reads it, with no second serialize.
Each commit still spends the edit, so a slider drag begins an edit per
frame; that serialize was already paid for Document, and Record adds a
deserialize only in frames where a drawer reads it.
The new test mutates the cached document between BeginEdit and the first
Record read; it fails on the lazy clone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
452f928 made nil the unset wire form, including dictionary keys: a
Dictionary<CultRecordRef<H>, float> holding the empty key encoded it as C0.
Python msgpack refuses nil map keys under strict_map_key, its default, so
cultcache-py cannot decode such a record. Nil cannot be the one canonical
form; "" can, and every runtime reads it.
The writer always writes "". The reader still takes nil as unset, because
452f928/b85a828 stores and Aetheria's Cut 9 import wrote nil; such a record
converts to "" on its next save and is byte-stable after that. The
store-composition contract names "" for value and key position, requires
readers to accept nil, and tells other runtimes to write "".
The tests pin payload bytes, so each claim is consequential: an unset ref
writes "", reloads through a fresh cache and resaves byte-identical; a
nil-written legacy payload reads as unset and resaves as ""; a ref-keyed
dictionary with an empty key writes "", reads back unset and resaves
byte-identical. The nil writer fails every byte assertion, a reader taking
nil as a set key fails the legacy pair, and a reader taking "" as a set key
fails the read-back assertions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rebuilds the tracked plugins with scripts/build-unity-package.ps1
-UpdateTemplate so they carry the unset-ref wire fix and
CultInspectorEdit.Record. The Studio package moves to 1.3.0 for
CultInspector.Record and depends on cultlib 1.0.58.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A drawer saw only its value; Aetheria's schematic-shape drawer needs sibling
members of the owning document (texture path, aspect, hull tints).
The model owns the seam: CultInspectorEdit.Record is a second copy of the
stored document as the edit began, cloned on first read and kept for the
edit. The Studio lowers it as CultInspector.Record, so a runtime CultUI
panel gets the same seam from the same edit. The Draw signature is
unchanged, so existing drawers keep compiling.
Why not hand drawers the edit copy the Studio already draws: that copy is
what Commit upserts, so a write through it would be saved without passing
through the returned value, GUI.changed, or read-only metadata. The record
copy is read by nothing, so writing to it saves nothing and changes neither
the edit nor the cache. It does not show edits other drawers made earlier in
the same frame.
The Studio's separate document-type field collapses into the held edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The formatter wrote default(CultRecordRef<T>) as nil but read nil back as
"", so re-upserting a loaded record changed its bytes (0xC0 became 0xA0).
One in-memory unset value: CultRecordKey.Value is never null, so
default(CultRecordRef<T>) equals a ref built from "". One wire form: an
empty key writes nil, and nil or a legacy "" reads back as default.
Load then save is byte-stable; a store holding "" converges to nil on its
next save.
Rust, TypeScript and Python have no reference type: a reference member is a
string in the consumer's own payload, so there is no sibling codec to align.
The store-composition contract now names nil as the one unset wire form.
The new test loads a record with an unset ref through a fresh cache,
re-upserts and flushes, and requires identical payload bytes; it fails on
the old formatter.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CultMath's hash is the shader-style frac(sin(x) * 43758.5453) and
returns float. Unity.Mathematics' hash returned a well-mixed uint, and
seeding from the float hash collapses: a 100x100 position grid gave 1625
distinct values in Aetheria's zone generator.
- math.pcg(uint), pcg3d(int3/float2/float3), and pcg4d(int4/float4)
follow Jarzynski and Olano, "Hash Functions for GPU Rendering"
(JCGT 9(3), 2020). pcg3d and pcg4d are the paper's code from page 32.
pcg is O'Neill's RXS-M-XS 32/32 output over one LCG step, with the
constants checked against pcg-c's pcg_variants.h.
- CultMath has no uint vectors, so pcg3d and pcg4d carry uint bit
patterns in int3/int4. The float overloads hash IEEE-754 bits via the
HLSL intrinsic asuint(float), which is new. pcg3d(float2) holds z at
0, the paper's route for 2 -> N inputs.
- The HLSL mirror gains the same functions, written over scalar uints
so the text compiles both in dxc 1.9.0.5402 (lib_6_3, HV 2021, clean)
and in C#. HlslSourceCompatibilityTests now compares int and uint
arguments and results by value; a wrong shift in the mirror's pcg4d
gives 208 mismatches.
- Known-answer vectors come from an independent Python transcription.
pcg3d separates a 100x100 half-unit grid (the full int3). One
component alone has one birthday collision there, as a 32-bit value
can. The same grid on the integer lattice has none.
- design.md states that the hashes are integer-exact in HLSL and on the
CPU: dxc wraps uint multiply and add modulo 2^32, as C# unchecked does.
- Bump to 0.2.3 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 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>
Bump the package and assembly version and rebuild the tracked Unity
DLL and pdb from a deterministic build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aetheria Cut 8a needs int2(float2), clamp(int, int, int), and
frac(double). Fill them in CultMath as dxc compiles them:
- generate-swizzles.ps1 emits same-size conversion constructors and
math functions between float, int, and bool vectors. dxc lowers
float to int as fptosi (toward zero; NaN and out-of-range undefined),
numeric to bool as != 0 (fcmp une, NaN is true), bool to numeric as
0 or 1. floatN(int) splat keeps floatN(0) from going ambiguous
against floatN(intN).
- Int and int-vector clamp are min(max(x, a), b), matching dxc's
IMin(IMax(x, a), b); inverted bounds return b.
- frac(double) keeps double precision. dxc demotes double frac to
float, as it does exp and lerp; CultMath's existing double overloads
already diverge that way, and design.md now says so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bumps org.gamecult.cultlib to 1.0.57 and rebuilds its managed plugins from
main, so the Unity package carries the store-routing CultCache (one home
store per type, AddBackingStore(CacheBackingStore, Type[]), atomic commit)
instead of the Sep 3 assemblies. Install docs point at the new tag.
The build script now publishes without Source Link or the source revision,
with CI path mapping and cleared Release intermediates. The previous DLLs
named the commit and the worktree they were built in, so committed plugins
could never match a fresh build of the commit that holds them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings Cuts 1-6 of the Aetheria CultCache migration (one home store per
type, fixed routes, atomic commit with compare-exchange, parked SoA) and
CultMath 0.2.0 onto main, which had moved on with the CultNet contract
consolidation. The two lines touch disjoint files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings codex/cultmath-hlsl-parity (d4c43ed) into the Cut 6 branch so Aetheria's
CultMath adoption and the CultCache Studio land on one line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
IsInitOnly reads the IsExternalInit required modifier, which is unverified on
Unity's Mono. Where it is not reported, init members show editable; reflection
assigns them anyway, so edits still round-trip. No code change: no cheaper
robust check exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unity's Quaternion showed eulerAngles beside x/y/z/w, Rect about eleven rows
over four values, and Bounds center/extents/size/min/max: settable properties
alias the fields they wrap. A struct with public fields now shows only those; a
struct without shows its settable properties, so record structs and CultMath
matrices (properties over private rows) still get clean rows and vectors show
fields only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The window lost its disabled state when record creation moved into the model:
Add and Create were enabled for abstract types and threw on click, and
CreateElement(type, type) on an abstract [Union] type threw "X is not a choice
for X". CultInspectorModel.CanCreate is now the one rule CreateDefault and the
window both read, a slot's own type is always a choice, and an abstract one
returns the "abstract or no parameterless constructor" notice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An exception thrown inside a `when` filter is swallowed as false, so a manifest
re-read failing mid-File.Replace turned a race that should retry into a thrown
torn-page error. The re-read now runs in the handler, and a failed re-read
counts as moved. Parallel.For's AggregateException is unwrapped where the pages
are read, so the page's own failure propagates and the torn check no longer
walks wrappers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mirror compiles under using static CultMath.math, so it proves composition,
not intrinsic rules; HlslSemanticsTests alone pins those. design.md also states
where FMin/FMax/Saturate turn NaN or infinity into numbers and tells callers to
check isnan first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dxc lowers double min/max to dx.op.binary.f64 opcodes 36/35 (FMin/FMax),
the same operations as float, so Math.Min/Math.Max (NaN-propagating) was the
wrong rule. Double min/max now use the float formulas, with NaN and signed-zero
assertions beside the float ones. Tracked Unity DLL rebuilt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The window decided whether a record type could be added
(GetConstructor(Type.EmptyTypes)), made it with Activator.CreateInstance,
and matched records to a type with IsAssignableFrom twice, duplicating
RecordCandidates.
Add and Create now ask the model's CreateElement, and its notice is the
refusal. The record list and the per-type counts come from
RecordCandidates. The window only renders.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Key identity serialized a key with the options of the key type's own
assembly, but the store writes it inside the document with the document
assembly's options, [CultCacheFormatterResolver] resolvers included. A key
that failed to serialize fell back to comparison by ToString.
The inspector codec now takes the owning document type, ReplaceKey and
FreshKey take it, and a key serializes as its declared key type under that
document's options, the store's own path. A key the document cannot
serialize is refused with a notice; the text fallback is deleted.
The comment and README said "the dictionary's own comparer"; it is the
rebuilt dictionary type's default comparer, and now says so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The copy was dropped only when a claimed drawer threw. A built-in drawing
exception (BuildDictionary, SetValue, a throwing constructor, Clone) or an
ExitGUIException escaped OnGUI with the copy alive, and the next change
committed the half-made edit.
The window now takes the copy out of _edit before drawing and puts it back
only when the document drew to the end with no drawer failing. Every
abnormal exit, ExitGUIException included, leaves the next frame to clone
afresh from the cache.
ExitGUIException is dropped too, not exempted: Unity's pickers exit on the
click that opens them and deliver the pick in a later event, which lands
on the fresh copy and commits normally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The unkeyed struct rule found auto-properties by the compiler's
<Name>k__BackingField name and marked the whole struct Unsupported when
one was get-only or a field was readonly. Record structs fell out with a
generic reason, and settable or init properties beside a field were
silently hidden. Editing is in place, so the lock protected nothing.
An unkeyed struct now shows its public fields (readonly ones read-only)
and its public properties that have a setter (init-only ones read-only).
Get-only properties are computed or kept by the in-place edit, so they are
not rows; vector swizzles stay out of the inspector.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A missing or hash-failing page under an unchanged manifest is corruption,
not a writer mid-commit, but every InvalidDataException was retried five
times and reported as "did not settle" with the real cause buried. The
retry now also requires the manifest bytes to have changed; otherwise the
corruption error is thrown on the first attempt. A vanished page directory
is a race under the same condition.
The reload test's writer now commits under every unleased read, so it only
settles if the retry takes the lease that commit created.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SequenceEqual on floats treats -0 as 0. The mirror test now compares
SingleToInt32Bits, with any NaN equal to any NaN. Its summary and
design.md now say the test proves CultMath.hlsl's text computes the same
float32 results as C# math on the CPU, not that a GPU agrees: driver sin
precision alone makes cultmath_hash and value noise differ on hardware.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dxc lowers float min/max/clamp to dx.op FMin/FMax (36/35) and saturate to
dx.op Saturate (7). DXIL.rst defines them: FMin(a, b) is a < b ? a : b,
FMax(a, b) is a >= b ? a : b, a NaN operand returns the other, and
Saturate is FMin(1, FMax(0, x)). C# used MathF.Min/Max, which pass NaN
through and order signed zeros, so saturate(NaN) was NaN instead of 0.
Every float overload now follows the spec, normalize uses the same max as
its mirror, and the mirror's saturate spells Saturate's operand order
(which maps -0 to +0, where clamp(x, 0, 1) keeps -0). Non-NaN results
change only in the sign of a zero. design.md records the rules and that
dxc marks float math fast, so GPU NaN results are not guaranteed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CreateElement returned null without a notice when the element type itself
had no parameterless constructor, and the Studio added that null. The
notice now follows the result: nothing made is refused, except the null
a Nullable<T> slot legitimately holds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit's DLL and pdb came from an incremental build that did
not recompile after the Source Link switch, so the pdb still carried the
commit URL. The script now builds with --no-incremental, so a stale obj
directory cannot decide what the byte check compares.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tracked DLL is committed with its source, so a build that embeds the
commit can never match it. Two places named the commit: the informational
version (0.2.0+<sha>) and Source Link's commit URL in the pdb, whose content
id the DLL also carries. The csproj drops the revision from the version, and
the Unity build turns Source Link off; NuGet packs keep it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
get-dxc.ps1 fetched releases/latest with no pin or hash, so the compiler that
defines parity could change under the tests. It now downloads v1.9.2607's
dxc_2026_07_29.zip (dxcompiler 1.9.0.5402) and refuses a zip whose SHA256 is
not A1DFB116...CD241. The hash was taken from the zip downloaded from
Microsoft's GitHub release and matches the digest GitHub publishes for the
asset. Script ran end to end and installed 1.9.0.5402.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only cultmath_snoise was compared, at four inputs, so a flipped cultmath_step,
a changed hash constant, and a snoise2 tie change all passed. The mirror is
now discovered by reflection: every cultmath_* overload with a math
counterpart (26 functions) runs over 104 deterministic cases - each special
value in every argument (0, -0, +-1, ties, 1e-7, +-1e4, 1e7, NaN), special
pairs, seeded randoms, and vectors with x == y (snoise's x0.x == x0.y) - and
must match exactly. Both sides are float32 C# over the same intrinsics, so no
tolerance. A cultmath_* function with no counterpart must be named in
MirrorOnly. The four-input snoise theory is gone.
Mutations: flipped step args fail (349 mismatches); hash constants changed
by more than one float32 ulp fail (43758.55: 467, 127.2: 289, 74.8: 97).
43758.5453 -> .5454 is the same float32 and cannot fail. snoise2 x0.x > x0.y
-> >= is invisible: over 1,829,415 exact ties the result is bit-identical,
because the off-simplex corner's kernel is <= 0 on the shared edge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A pull takes the commit lease only when .commit.lock exists. Without it
(no writer has created one yet, or the store was copied without it) the
reader loaded with no lease, and a writer committing between its manifest
read and its page reads left it with a vanished page or a stale manifest.
An unleased pull now keeps its load only if the manifest bytes it read are
still current afterwards; a changed manifest, or a named page that is
missing or fails its content hash, reloads it, at most five times, and a
pull that has not settled by then throws. A retry takes the lease once a
writer has created it. Opening still creates nothing. The lease comment no
longer claims a missing lock means no writer ever committed, and the store
composition contract states the rule.
The test deletes the lock from a seeded store and commits from a second
cache through an internal seam between the manifest read and the page
reads, once replacing the page the reader named and once only adding a key.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
soft was documented as ignored and changed nothing. Its only caller,
Mimir.CultMeshMedia, now calls FlushAsync().
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The routing branch drops the org.gamecult.cultmath dependency from
src/GameCult.Unity/Assets/Caching/package.json; bumping it to 0.2.0 here only
manufactured a merge conflict. The file returns to the merge base (8be174b).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dxc 1.9.0.5402 (dxc_2026_07_29, lib_6_3, -HV 2021, -Od) lowers step(e, x) to
fcmp olt x, e; select 0 : 1, so NaN on either side gives 1. The previous
x >= e ? 1 : 0 rule came from prose, not the compiler. math.step and
cultmath_step now use x < e ? 0 : 1 for every overload; the mirror's
cultmath_step compiles to the same fcmp/select as the intrinsic. Unity shader
copy synced; DLL rebuilt and byte-checked by build-unity-package.ps1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The model paragraph now lists the edit decisions the model owns (key
refusal by bytes and by the dictionary comparer, fresh keys, element
choices, integer clamping) and names CreateInspectorModel. The drawer
section states the return-and-GUI.changed contract and the discarded
frame on a throw. The sample drawer draws a hex color string instead of
CultMath.float3, which the package no longer references.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A drawer that threw after mutating a reference value in place left that
half-made mutation in the live edit copy, where the next unrelated change
saved it. DrawDocument now reports a drawer failure, and the window drops
the edit copy uncommitted so the next frame re-clones from the cache; the
error row says nothing from that frame was saved.
The drawer contract no longer invites in-place mutation: a change returns
the new value and sets GUI.changed, and an in-place mutation without
GUI.changed is not saved reliably. Detecting such a mutation would mean
serializing the document every frame, so it is documented, not detected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ElementChoices says what a new list element, dictionary value or union
pick may be (a union's declared subtypes, or the slot type itself), and
CreateElement makes one or returns a notice when a subtype has no
parameterless constructor. It replaces CreateUnionValue, whose throw the
IMGUI caught and sent to the console. A dictionary with a union value
type now offers the subtypes too instead of adding a null value.
NarrowInteger clamps an integer edit to its type's range; the IMGUI no
longer converts and swallows overflow itself. IMGUI renders the model's
answers and shows its notices.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ruling: the heuristic that rebuilt unkeyed structs through a
guessed constructor is removed, not guarded. Gone: the Composite value
kind, CultInspectorShape.Composer, CultInspectorModel.Compose and
Composite, the composite-component flag on CultInspectorMember, the IMGUI
composite rebuild path, and the README text about it.
An unkeyed struct is now edited in place through its public fields. A
struct holding state it cannot write in place (a public readonly field or
a get-only auto-property) is Unsupported with a reason naming the member,
until a drawer claims it. Computed get-only properties are not state and
do not lock a struct.
The composite-rejection test goes with the heuristic; the replacement
test pins both lock rules and fails with either removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A refused key and an unavailable fresh key are now the model's answer:
ReplaceKey returns the kept key with the notice text, FreshKey returns
null with its notice, and IMGUI only shows them.
A key is taken when another serializes the same or when the dictionary's
own comparer calls them equal, so 0.0 against -0.0 is refused instead of
throwing in BuildDictionary.
Keys never actually compared by bytes: the Studio handed the model the
document codec, whose registry guard throws for any non-document value,
so every key fell back to ToString. CultCacheMessagePack now builds the
model with a value serializer, and the Studio and tests both use it.
Tests pin the comparer rule, the unchanged-key rule (an old empty
reference key survives an edit of its value) and serialized identity
(keys whose ToString collides); each fails with its rule removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A keyed type that hides a base member with `new` offered two candidates
for one name. The inspector now keeps the most-derived declaration, after
dropping [IgnoreMember] ones, as the registry's own-member scan does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fresh deterministic netstandard2.1 build from the CRLF working copy a checkout
produces; build-unity-package.ps1 byte check passes. The DLL grows from
66,048 to 297,472 bytes, almost all generated swizzles.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
design.md states the generated swizzle and mixed-constructor surface, writable
matrix rows, HLSL intrinsic types and NaN behavior, the deferred matrix items
(float4x4, transpose, determinant, _11 names), the exact HLSL-to-C#
transformations the compatibility test applies, and the dxc check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
build-unity-package.ps1 read the bridge's file names to decide whether it
re-declared core CultMath sources. It now parses declarations: core type
names come from src/CultMath (comments stripped), and every bridge source may
declare types only in CultMath.UnityBridge and never a core type name.
Probed by injecting "struct float3" into UnityConversions.cs: the script
throws.
Strips the UTF-8 BOMs this branch added to both Unity package.json files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
HlslSourceCompatibilityTests now reads shaders/CultMath.hlsl, applies only
the documented transformations (drop include guards, drop Texture2D/
SamplerState functions, file-scope static const -> const, f literal
suffixes, wrap in a class), and compiles it with Roslyn against CultMath.
Any compile error fails the test (probed with an undeclared call). The
compiled cultmath_snoise(float3/float2) also matches math.snoise.
dxc -T lib_6_3 -HV 2021 rejected the mirror: cultmath_snoise(float2) called
the undeclared cultmath_simplex_mod289/permute. Renamed to the declared
cultmath_snoise_* helpers. Vector const locals became plain locals (C# const
is primitives only), and cultmath_step uses value >= edge like HLSL step and
the C# mirror. dxc 1.9.0.5402 now compiles the file cleanly. The Unity
package's Shaders copy is synced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tools/generate-swizzles.ps1 writes the checked-in src/CultMath/Swizzles.g.cs
as partial structs plus a partial math: for float2/3/4, int2/3/4 and bool2/3/4
every swizzle of length 2-4 including repeats, in xyzw and rgba naming, with
setters only on non-repeating masks, plus single-component r/g/b/a. double2/3
get lengths 2-3 (there is no double4). Every HLSL mixed constructor
(float4(float2, float2), float4(float, float2, float), copy forms, ...) exists
as a struct constructor and, for float/int/bool, a math function. The
hand-written swizzles and mixed constructors it supersedes are deleted.
Matrices store private rows and return ref float2/float3 from the row
indexer ([UnscopedRef], polyfilled for netstandard2.1), so m[1][2] = s
compiles as in HLSL; _mRC are get/set properties over the rows. Equality and
hashing stay value-based.
Intrinsics follow HLSL: sign returns int/intN and yields 0 for NaN; step is
x >= y ? 1 : 0 so NaN yields 0; any/all accept float and int vectors;
min/max/abs on int vectors return int vectors, and abs(int) returns int.
Strips the UTF-8 BOMs this branch added to math.cs, CultMath.csproj and
HlslSemanticsTests.cs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>