{"record":{"id":"0cb7b2601b397db4","repo":"egametang/ET","slug":"invalid-tile-index","errorCode":null,"errorMessage":"Invalid tile index","messagePattern":"Invalid tile index","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"Packages/cn.etetet.recast/Scripts/Core/Share/Detour.TileCache/DtTileCache.cs","lineNumber":297,"sourceCode":"        }\n\n        private int Align4(int i)\n        {\n            return (i + 3) & (~3);\n        }\n\n        public void RemoveTile(long refs)\n        {\n            if (refs == 0)\n            {\n                throw new Exception(\"Invalid tile ref\");\n            }\n\n            int tileIndex = DecodeTileIdTile(refs);\n            int tileSalt = DecodeTileIdSalt(refs);\n            if (tileIndex >= m_params.maxTiles)\n            {\n                throw new Exception(\"Invalid tile index\");\n            }\n\n            DtCompressedTile tile = m_tiles[tileIndex];\n            if (tile.salt != tileSalt)\n            {\n                throw new Exception(\"Invalid tile salt\");\n            }\n\n            // Remove tile from hash lookup.\n            int h = DtNavMesh.ComputeTileHash(tile.header.tx, tile.header.ty, m_tileLutMask);\n            DtCompressedTile prev = null;\n            DtCompressedTile cur = m_posLookup[h];\n            while (cur != null)\n            {\n                if (cur == tile)\n                {\n                    if (prev != null)\n                    {","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/egametang/ET/blob/5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f/Packages/cn.etetet.recast/Scripts/Core/Share/Detour.TileCache/DtTileCache.cs#L279-L315","documentation":"After decoding a tile ref into its index, RemoveTile checks the index against maxTiles. A decoded index at or beyond the array bound means the ref did not originate from this cache (different capacity, or corrupt/stale bytes). The cache refuses to dereference m_tiles[tileIndex] to avoid an out-of-bounds access.","triggerScenarios":"Passing a ref obtained from a different DtTileCache instance (whose maxTiles/tileBits differ); a ref corrupted in serialization or truncated; a ref from a cache that was rebuilt with a smaller maxTiles.","commonSituations":"Sharing tile refs across caches/levels; persisting a ref to disk and reloading into a cache with different params; copying a ref between sessions where the world config changed.","solutions":["Do not exchange tile refs between DtTileCache instances; look up fresh via GetTileRef(tx,ty,layer) on the target cache.","Treat refs as ephemeral per cache lifetime; do not persist them across config changes.","Before removing, verify the ref still resolves: GetTileRef by position and compare."],"exampleFix":"// before: ref came from another cache / a previous session\ncache.RemoveTile(persistedRef);\n\n// after: resolve against the current cache by position\nlong ref = cache.GetTileRef(tx, ty, layer);\nif (ref != 0) cache.RemoveTile(ref);","handlingStrategy":"validation","validationCode":"// Resolve the ref against THIS cache by position before removing\nlong ref = cache.GetTileRef(tx, ty, layer);\nif (ref == 0) return;\ncache.RemoveTile(ref);","typeGuard":null,"tryCatchPattern":"try { cache.RemoveTile(ref); }\ncatch (Exception e) when (e.Message.Contains(\"Invalid tile index\"))\n{ /* ref is foreign/stale; re-resolve by position or drop it */ }","preventionTips":["Never share tile refs between DtTileCache instances.","Treat refs as ephemeral per cache lifetime.","Resolve refs by position right before use."],"tags":["validation","detour-tilecache","stale-ref"],"backgroundTag":null,"analyzedSha":"5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f","analyzedAt":"2026-08-13T21:10:40.377Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}