Skip to content

[UUM-148935] Make VectorHash quantization runtime-independent - #700

Open
lopezt-unity wants to merge 1 commit into
masterfrom
fix/uum-148935-vectorhash-determinism
Open

[UUM-148935] Make VectorHash quantization runtime-independent#700
lopezt-unity wants to merge 1 commit into
masterfrom
fix/uum-148935-vectorhash-determinism

Conversation

@lopezt-unity

@lopezt-unity lopezt-unity commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Purpose of this PR

Ports the fix from unity/unity PR 123982, which fixed UUM-148935 and UUM-133532 (formerly UUM-111993) in the vendored ShadowPackages/com.unity.probuilder snapshot in the engine repo. That PR's own description notes the fix must land here too, or it will be silently reverted on the next ShadowPackages refresh.

VectorHash.HashFloat hashed a float via (ulong)(f * FltCompareResolution). Casting an out-of-range or NaN float to ulong is unspecified by ECMA-335: Mono on x64 wraps around, CoreCLR (and Mono on Arm64) saturate — collapsing every negative product to 0. That single cause explains all three IntVectorTests failures. HashFloat is replaced by VectorHash.RoundToInt, which maps NaN to 0, clamps explicitly instead of relying on the cast, and rounds half to even — deterministic on every runtime and architecture.

Second defect, present on every runtime today

IntVec2/IntVec3/IntVec4.round quantized via Convert.ToInt32(v * FltCompareResolution) while HashFloat truncated — a GetHashCode/Equals contract violation. SharedVertex.GetSharedVerticesWithPositions and ObjExporter use IntVec3 as a Dictionary key, so coincident vertices could land in different buckets and silently fail to weld. Routing both through RoundToInt fixes this too, and drops an OverflowException that round() threw on NaN/infinite positions.

Test changes

  • The three IntVectorTests no longer need a CoreCLR-disable guard, since the underlying hashing bug — not CoreCLR itself — was the cause.
  • TestVectorHashOverflow's expectations are updated to the new well-defined values (and a NegativeInfinity case is added).
  • Added TestEqualIntVec3SharesHashCode, which asserts the Equals/GetHashCode agreement directly.

Release Notes

See CHANGELOG.md.

Reviewed by

Reviewed against the merged engine-repo PR (unity/unity#123982) and the linked Jira ticket (UUM-148935); logic and test-expectation changes were independently re-derived and checked for consistency rather than copied blind.

VectorHash.HashFloat hashed a float by casting it to ulong. ECMA-335
leaves conv.u8 on an out-of-range or NaN float unspecified, so each
JIT emits whatever the underlying hardware instruction yields: Mono
on x64 wraps around, CoreCLR (and Mono on Arm64) saturate. Saturation
clamps every negative product to 0, causing hash collisions and
equality/hashcode mismatches that are especially visible on CoreCLR
and on macOS13 Arm64.

HashFloat is replaced by VectorHash.RoundToInt, which maps NaN to 0,
clamps explicitly instead of relying on the cast, and rounds half to
even. IntVec2/IntVec3/IntVec4.round now call the same function
instead of duplicating the quantization via Convert.ToInt32, which
rounded differently than HashFloat truncated and caused GetHashCode
to disagree with Equals for some coincident positions.

Ported from unity/unity PR 123982, which fixed this in the
ShadowPackages vendored snapshot of this package; this is the
upstream fix that needs to land here so the next ShadowPackages
refresh doesn't silently revert it.

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Ship it

The change makes vector quantization explicit and shares it with integer-vector equality; I examined the affected equality/hash consumers, welding and export dictionary paths, picker usage, and the updated tests.

Reviewed commit 9b9d77f

🤖 Helpful? 👍/👎

@modrimkus-unity modrimkus-unity left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@codecov-github-com

codecov-github-com Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

@@            Coverage Diff             @@
##           master     #700      +/-   ##
==========================================
+ Coverage   38.43%   38.57%   +0.13%     
==========================================
  Files         279      279              
  Lines       39164    39201      +37     
==========================================
+ Hits        15052    15121      +69     
+ Misses      24112    24080      -32     
Flag Coverage Δ
probuilder_MacOS_6000.0 36.29% <100.00%> (+0.16%) ⬆️
probuilder_MacOS_6000.3 36.29% <100.00%> (+0.16%) ⬆️
probuilder_MacOS_6000.4 36.29% <100.00%> (+0.16%) ⬆️
probuilder_MacOS_6000.5 36.29% <100.00%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
Runtime/Core/IntVec2.cs 40.00% <100.00%> (ø)
Runtime/Core/IntVec3.cs 66.66% <100.00%> (+8.33%) ⬆️
Runtime/Core/IntVec4.cs 43.24% <100.00%> (ø)
Runtime/Core/VectorHash.cs 100.00% <100.00%> (ø)

... and 7 files with indirect coverage changes

ℹ️ Need help interpreting these results?

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.

2 participants