egametang/ET · error · Exception

Invalid tile index

Error message

Invalid tile index

What it means

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.

Source

Thrown at Packages/cn.etetet.recast/Scripts/Core/Share/Detour.TileCache/DtTileCache.cs:297

        }

        private int Align4(int i)
        {
            return (i + 3) & (~3);
        }

        public void RemoveTile(long refs)
        {
            if (refs == 0)
            {
                throw new Exception("Invalid tile ref");
            }

            int tileIndex = DecodeTileIdTile(refs);
            int tileSalt = DecodeTileIdSalt(refs);
            if (tileIndex >= m_params.maxTiles)
            {
                throw new Exception("Invalid tile index");
            }

            DtCompressedTile tile = m_tiles[tileIndex];
            if (tile.salt != tileSalt)
            {
                throw new Exception("Invalid tile salt");
            }

            // Remove tile from hash lookup.
            int h = DtNavMesh.ComputeTileHash(tile.header.tx, tile.header.ty, m_tileLutMask);
            DtCompressedTile prev = null;
            DtCompressedTile cur = m_posLookup[h];
            while (cur != null)
            {
                if (cur == tile)
                {
                    if (prev != null)
                    {

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Do not exchange tile refs between DtTileCache instances; look up fresh via GetTileRef(tx,ty,layer) on the target cache.
  2. Treat refs as ephemeral per cache lifetime; do not persist them across config changes.
  3. Before removing, verify the ref still resolves: GetTileRef by position and compare.

Example fix

// before: ref came from another cache / a previous session
cache.RemoveTile(persistedRef);

// after: resolve against the current cache by position
long ref = cache.GetTileRef(tx, ty, layer);
if (ref != 0) cache.RemoveTile(ref);
Defensive patterns

Strategy: validation

Validate before calling

// Resolve the ref against THIS cache by position before removing
long ref = cache.GetTileRef(tx, ty, layer);
if (ref == 0) return;
cache.RemoveTile(ref);

Try / catch

try { cache.RemoveTile(ref); }
catch (Exception e) when (e.Message.Contains("Invalid tile index"))
{ /* ref is foreign/stale; re-resolve by position or drop it */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/0cb7b2601b397db4. Report an issue: GitHub.