egametang/ET · critical · Exception
Too few salt bits: {m_saltBits}
Error message
Too few salt bits: {m_saltBits} What it means
DtTileCache packs each tile reference into a 31-bit value split into a tile-index portion and a 'salt' portion. The salt lets the cache detect references to tiles that have since been recycled. The constructor derives m_tileBits = ilog2(nextPow2(maxTiles)) and m_saltBits = min(31, 32 - m_tileBits); if fewer than 10 salt bits remain, reference integrity degrades and the constructor refuses to build the cache. This fires once, at construction, so the DtTileCache is never usable until params change.
Source
Thrown at Packages/cn.etetet.recast/Scripts/Core/Share/Detour.TileCache/DtTileCache.cs:84
{
m_tileLutSize = 1;
}
m_tileLutMask = m_tileLutSize - 1;
m_tiles = new DtCompressedTile[m_params.maxTiles];
m_posLookup = new DtCompressedTile[m_tileLutSize];
for (int i = m_params.maxTiles - 1; i >= 0; --i)
{
m_tiles[i] = new DtCompressedTile(i);
m_tiles[i].next = m_nextFreeTile;
m_nextFreeTile = m_tiles[i];
}
m_tileBits = DtUtils.Ilog2(DtUtils.NextPow2(m_params.maxTiles));
m_saltBits = Math.Min(31, 32 - m_tileBits);
if (m_saltBits < 10)
{
throw new Exception("Too few salt bits: " + m_saltBits);
}
}
private bool Contains(List<long> a, long v)
{
return a.Contains(v);
}
/// Encodes a tile id.
private long EncodeTileId(int salt, int it)
{
return ((long)salt << m_tileBits) | (long)it;
}
/// Decodes a tile salt.
private int DecodeTileIdSalt(long refs)
{
long saltMask = (1L << m_saltBits) - 1;View on GitHub (pinned to 5cab01f7a8)
Solutions
- Lower DtTileCacheParams.maxTiles to the smallest power-of-two that covers the real concurrent tile count (commonly a few thousand), so ilog2(nextPow2(maxTiles)) <= 22.
- If you genuinely need more than ~4M tiles, partition the world into multiple DtTileCache instances (one per region/streaming chunk) instead of one giant cache.
- Assert at config-load time that Ilog2(NextPow2(maxTiles)) <= 22 so the failure surfaces in your own pipeline with a clearer message.
Example fix
// before
var p = new DtTileCacheParams { maxTiles = 8_000_000, ... };
var tc = new DtTileCache(p, storage, mesh, comp, proc);
// after: size to actual need, power of two
var p = new DtTileCacheParams { maxTiles = 4096, ... };
System.Diagnostics.Debug.Assert(
DotRecast.Recast.DtUtils.Ilog2(DotRecast.Recast.DtUtils.NextPow2(p.maxTiles)) <= 22);
var tc = new DtTileCache(p, storage, mesh, comp, proc); Defensive patterns
Strategy: validation
Validate before calling
// Run before constructing DtTileCache
int tileBits = DtUtils.Ilog2(DtUtils.NextPow2(p.maxTiles));
int saltBits = Math.Min(31, 32 - tileBits);
if (saltBits < 10)
throw new ArgumentException(
$"maxTiles={p.maxTiles} leaves only {saltBits} salt bits (need >=10). " +
"Reduce maxTiles (<= ~4,194,304) or split into multiple caches."); Prevention
- Clamp DtTileCacheParams.maxTiles to the smallest power of two that covers peak live tiles.
- Assert saltBits >= 10 in your config/validation layer so failures surface with a clear message.
- Partition very large worlds into multiple DtTileCache instances rather than one oversized cache.
When it happens
Trigger: Constructing `new DtTileCache(DtTileCacheParams option, ...)` where `option.maxTiles` is so large that `DtUtils.Ilog2(DtUtils.NextPow2(option.maxTiles))` exceeds 22 (i.e. nextPow2(maxTiles) >= 2^23, so maxTiles beyond ~4.19 million). Happens when maxTiles is set to a huge literal or copied from another cache without scaling.
Common situations: Setting maxTiles to a very large number 'to be safe' (e.g. int.MaxValue / a million); reusing DtTileCacheParams from a different world size; generating params programmatically and forgetting to clamp; non-power-of-two huge values that nextPow2 rounds up even further.
Related errors
- Out of storage
- Buffer too small
- ERR_LocationPrimaryUnavailable
- location proxy message sender is null: {self.Root().Name}
- hot update assembly name cann't be empty
AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13).
Data as JSON: /api/errors/f910aefe5d91a442.
Report an issue: GitHub.