{"record":{"id":"f910aefe5d91a442","repo":"egametang/ET","slug":"too-few-salt-bits-m-saltbits","errorCode":null,"errorMessage":"Too few salt bits: {m_saltBits}","messagePattern":"Too few salt bits: (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"critical","filePath":"Packages/cn.etetet.recast/Scripts/Core/Share/Detour.TileCache/DtTileCache.cs","lineNumber":84,"sourceCode":"            {\n                m_tileLutSize = 1;\n            }\n\n            m_tileLutMask = m_tileLutSize - 1;\n            m_tiles = new DtCompressedTile[m_params.maxTiles];\n            m_posLookup = new DtCompressedTile[m_tileLutSize];\n            for (int i = m_params.maxTiles - 1; i >= 0; --i)\n            {\n                m_tiles[i] = new DtCompressedTile(i);\n                m_tiles[i].next = m_nextFreeTile;\n                m_nextFreeTile = m_tiles[i];\n            }\n\n            m_tileBits = DtUtils.Ilog2(DtUtils.NextPow2(m_params.maxTiles));\n            m_saltBits = Math.Min(31, 32 - m_tileBits);\n            if (m_saltBits < 10)\n            {\n                throw new Exception(\"Too few salt bits: \" + m_saltBits);\n            }\n        }\n\n        private bool Contains(List<long> a, long v)\n        {\n            return a.Contains(v);\n        }\n\n        /// Encodes a tile id.\n        private long EncodeTileId(int salt, int it)\n        {\n            return ((long)salt << m_tileBits) | (long)it;\n        }\n\n        /// Decodes a tile salt.\n        private int DecodeTileIdSalt(long refs)\n        {\n            long saltMask = (1L << m_saltBits) - 1;","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/egametang/ET/blob/5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f/Packages/cn.etetet.recast/Scripts/Core/Share/Detour.TileCache/DtTileCache.cs#L66-L102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nvar p = new DtTileCacheParams { maxTiles = 8_000_000, ... };\nvar tc = new DtTileCache(p, storage, mesh, comp, proc);\n\n// after: size to actual need, power of two\nvar p = new DtTileCacheParams { maxTiles = 4096, ... };\nSystem.Diagnostics.Debug.Assert(\n    DotRecast.Recast.DtUtils.Ilog2(DotRecast.Recast.DtUtils.NextPow2(p.maxTiles)) <= 22);\nvar tc = new DtTileCache(p, storage, mesh, comp, proc);","handlingStrategy":"validation","validationCode":"// Run before constructing DtTileCache\nint tileBits = DtUtils.Ilog2(DtUtils.NextPow2(p.maxTiles));\nint saltBits = Math.Min(31, 32 - tileBits);\nif (saltBits < 10)\n    throw new ArgumentException(\n        $\"maxTiles={p.maxTiles} leaves only {saltBits} salt bits (need >=10). \" +\n        \"Reduce maxTiles (<= ~4,194,304) or split into multiple caches.\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["configuration","detour-tilecache","constructor","capacity"],"backgroundTag":null,"analyzedSha":"5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f","analyzedAt":"2026-08-13T21:10:40.377Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}