fix(render): eliminate EnvCell identity collisions

This commit is contained in:
Erik 2026-07-24 13:44:56 +02:00
parent 999201cca7
commit b7b9aaa9dd
7 changed files with 129 additions and 23 deletions

View file

@ -3,21 +3,52 @@ using System.Collections.Generic;
namespace AcDream.Core.Rendering.Wb;
/// <summary>
/// Computes WorldBuilder's content identity for an EnvCell shell.
/// Computes acdream's collision-resistant content identity for an EnvCell shell.
/// Equal environment, cell-structure, and ordered surface tuples share one
/// prepared geometry payload even when they occur under different cell IDs.
/// </summary>
public static class EnvCellGeometryIdentity
{
private const ulong FnvOffsetBasis = 14695981039346656037UL;
private const ulong FnvPrime = 1099511628211UL;
private const ulong PayloadMask = 0x0FFF_FFFD_FFFF_FFFFUL;
private const ulong Namespace = 0xE000_0002_0000_0000UL;
/// <summary>
/// Source: WorldBuilder <c>EnvCellRenderManager.GetEnvCellGeomId</c>,
/// lines 94-103. Bit 33 separates content identities from per-cell IDs,
/// whose synthetic geometry requests use bit 32.
/// Hashes the complete typed tuple with FNV-1a and places it in a dedicated
/// high-bit namespace. Bit 33 remains set for compatibility with existing
/// synthetic-geometry diagnostics; the top nibble prevents overlap with
/// DAT IDs and older low-bit synthetic IDs.
///
/// WorldBuilder's original 31× polynomial is retained in
/// <see cref="ComputeLegacyWorldBuilder"/> for conformance evidence, but it
/// is not a valid resource identity: installed retail DAT cells 0x00030175
/// and 0x01BC0105 produce the same value for different geometry tuples.
/// </summary>
public static ulong Compute(
uint environmentId,
ushort cellStructure,
IReadOnlyList<ushort> surfaces)
{
ulong hash = FnvOffsetBasis;
AddUInt32(ref hash, environmentId);
AddUInt16(ref hash, cellStructure);
AddUInt32(ref hash, checked((uint)surfaces.Count));
for (int i = 0; i < surfaces.Count; i++)
AddUInt16(ref hash, surfaces[i]);
return (hash & PayloadMask) | Namespace;
}
/// <summary>
/// Verbatim WorldBuilder <c>EnvCellRenderManager.GetEnvCellGeomId</c>
/// lines 94-103. Kept only to demonstrate and regression-test the real DAT
/// collision that required the stronger internal identity.
/// </summary>
public static ulong ComputeLegacyWorldBuilder(
uint environmentId,
ushort cellStructure,
IReadOnlyList<ushort> surfaces)
{
long hash = 17;
hash = unchecked(hash * 31 + environmentId);
@ -26,4 +57,24 @@ public static class EnvCellGeometryIdentity
hash = unchecked(hash * 31 + surfaces[i]);
return (ulong)hash | 0x2_0000_0000UL;
}
private static void AddUInt16(ref ulong hash, ushort value)
{
AddByte(ref hash, (byte)value);
AddByte(ref hash, (byte)(value >> 8));
}
private static void AddUInt32(ref ulong hash, uint value)
{
AddByte(ref hash, (byte)value);
AddByte(ref hash, (byte)(value >> 8));
AddByte(ref hash, (byte)(value >> 16));
AddByte(ref hash, (byte)(value >> 24));
}
private static void AddByte(ref ulong hash, byte value)
{
hash ^= value;
hash = unchecked(hash * FnvPrime);
}
}