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

@ -120,7 +120,9 @@ public sealed class EnvCellLandblockBuildBuilder
}
/// <summary>
/// Source: WB EnvCellRenderManager.cs:94-103 (verbatim arithmetic).
/// Collision-resistant successor to WB EnvCellRenderManager.cs:94-103.
/// The legacy polynomial and its installed-DAT collision remain covered by
/// Core conformance tests.
/// </summary>
public static ulong ComputeGeometryId(
uint environmentId,

View file

@ -4,7 +4,7 @@
// four reverted RR7 variants couldn't fix.
//
// Sources ported byte-for-byte:
// GetEnvCellGeomId <- WB EnvCellRenderManager.cs:94-103
// GetEnvCellGeomId <- collision-resistant successor to WB's content key
// PrepareRenderBatches <- WB EnvCellRenderManager.cs:247-373
// Render(filter:) <- WB EnvCellRenderManager.cs:395-511
// RenderModernMDIInternal <- WB BaseObjectRenderManager.cs:709-848 (single-slot variant)
@ -353,14 +353,15 @@ public sealed unsafe class EnvCellRenderer : IDisposable
// ---------------------------------------------------------------------------
// GetEnvCellGeomId
// Verbatim copy of WB EnvCellRenderManager.cs:94-103.
// Shared collision-resistant content identity. Core retains WB's original
// polynomial for conformance evidence and the installed-DAT collision test.
// ---------------------------------------------------------------------------
/// <summary>
/// Returns a deduplicated geometry ID for an EnvCell based on its environment,
/// cell structure index, and surface IDs. Bit 33 is set to distinguish from
/// per-cell IDs (which use bit 32).
/// Source: WB EnvCellRenderManager.cs:94-103 (verbatim).
/// See <see cref="AcDream.Core.Rendering.Wb.EnvCellGeometryIdentity"/>.
/// </summary>
public static ulong GetEnvCellGeomId(uint environmentId, ushort cellStructure, List<ushort> surfaces)
=> EnvCellLandblockBuildBuilder.ComputeGeometryId(

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);
}
}