Interface DynamicRegion


public interface DynamicRegion
The scene's dynamic-region (instance) chunk-descriptor grid (v19+).

RS3 does not build instances — player-owned houses, Dungeoneering floors, boss rooms — out of dedicated map data. It assembles them by stamping 8x8-tile chunks copied out of ordinary static regions, following a table the server sends when it rebuilds the scene. This interface is that table, published verbatim in the snapshot, plus the arithmetic to read it.

The reason it exists: a navigation layer baked against static map data is blind inside an instance. Given this grid it stops being blind — every instance tile can be mapped back to the static tile it was copied from, and the baked data for that tile is the answer.

Reading it

The one call most callers want is sourceOf(int, int, int):

  DynamicRegion region = snapshot.dynamicRegion();
  region.sourceOf(self.tileX(), self.tileY(), self.plane())
        .ifPresent(src -> log.info("standing on a copy of {},{}", src.tileX(), src.tileY()));

Per-tile loops should use sourceOfPacked(int, int, int) instead, which returns the same answer as a packed long and allocates nothing.

Lifetime

The instance handed out by GameSnapshot.dynamicRegion() is a flyweight over shared memory and inherits the snapshot's tick lifetime — the producer may overwrite the bytes it points at as soon as the next snapshot is published. Reconstructing instanced collision is not a within-one-tick job, so anything that needs to outlive the tick must take a heap copy with copyOf(DynamicRegion) and key it on GameSnapshot.sceneVersion().

Three ways this surprises people

  1. UNITS TRAP. originMapX() and maxMapX() are mapsquares (64 tiles); gridW() and gridH() are chunks (8 tiles). One mapsquare is 8 chunks. The conversion happens exactly once, inside chunkDescriptorAt(int, int, int) — callers pass world tiles and never do it themselves.
  2. maxMapX()/maxMapY() do not bound the resolvable area. They describe the client's loaded window, which can be larger than the descriptor grid — a live capture had a 5x5 mapsquare window over a 32x32 chunk (4x4 mapsquare) grid. "The tile is inside the loaded window" therefore does not imply "the tile has a source". An empty sourceOf(int, int, int) for a tile you can see is normal, not a bug.
  3. Rotation has never been witnessed non-zero. The semantics come from the client's own explicit switch and are high-confidence, but every instance captured so far (a player-owned house is a flat 1:1 contiguous copy) had rotation == 0 throughout. rotateLocalX(int, int, int), rotateLocalY(int, int, int) and rotateLocRotation(int, int) are covered by unit tests only. Treat pathing through a rotated instance as unvalidated until someone captures a Dungeoneering floor.

Mirrors ipc::DynamicRegion + dynChunks[] in NXTLibrary's SharedLayout.h. The byte offsets live on the host's wire-layout constants; the bit layout of a descriptor lives here, because scripts decode it and this module cannot depend on the host.

  • Field Details

    • PLANE_COUNT

      static final int PLANE_COUNT
      Planes in a scene, and the outer dimension of the descriptor grid.
      See Also:
    • CHUNK_TILES

      static final int CHUNK_TILES
      Tiles along one edge of a chunk. The unit the whole descriptor table is in.
      See Also:
    • CHUNK_SHIFT

      static final int CHUNK_SHIFT
      tileX >> CHUNK_SHIFT is the chunk X containing that tile.
      See Also:
    • CHUNK_TILE_MASK

      static final int CHUNK_TILE_MASK
      tileX & CHUNK_TILE_MASK is the tile's position within its chunk. A mask: only ever the right operand of an &.
      See Also:
    • CHUNK_MAX_LOCAL

      static final int CHUNK_MAX_LOCAL
      Highest local coordinate inside a chunk — the pivot the rotation math reflects around, as in CHUNK_MAX_LOCAL - localX. Numerically equal to CHUNK_TILE_MASK, but a coordinate, not a mask: "mask minus x" is nonsense, and keeping the two spellings apart is what stops the next reader from believing it.
      See Also:
    • MAPSQUARE_CHUNK_SHIFT

      static final int MAPSQUARE_CHUNK_SHIFT
      mapX << MAPSQUARE_CHUNK_SHIFT converts a mapsquare to its first chunk — the x8 in the UNITS TRAP above.
      See Also:
    • ROTATION_COUNT

      static final int ROTATION_COUNT
      Rotations are 0..3 in 90-degree steps.
      See Also:
    • MAX_ROTATION

      static final int MAX_ROTATION
      Highest valid rotation — the inclusive bound for a range check.
      See Also:
    • ROTATION_MASK

      static final int ROTATION_MASK
      Composing two rotations wraps with this mask. Numerically equal to MAX_ROTATION; a mask is not a bound, so don't swap them.
      See Also:
    • SCENE_MODE_STATIC

      static final int SCENE_MODE_STATIC
      sceneMode() value for an ordinary, non-instanced scene.
      See Also:
    • NO_CHUNK

      static final int NO_CHUNK
      Descriptor value meaning "this grid cell has no source chunk".
      See Also:
    • DESC_PLANE_SHIFT

      static final int DESC_PLANE_SHIFT
      See Also:
    • DESC_PLANE_MASK

      static final int DESC_PLANE_MASK
      See Also:
    • DESC_CHUNK_X_SHIFT

      static final int DESC_CHUNK_X_SHIFT
      See Also:
    • DESC_CHUNK_X_MASK

      static final int DESC_CHUNK_X_MASK
      See Also:
    • DESC_CHUNK_Y_SHIFT

      static final int DESC_CHUNK_Y_SHIFT
      See Also:
    • DESC_CHUNK_Y_MASK

      static final int DESC_CHUNK_Y_MASK
      See Also:
    • DESC_ROTATION_SHIFT

      static final int DESC_ROTATION_SHIFT
      See Also:
    • DESC_ROTATION_MASK

      static final int DESC_ROTATION_MASK
      See Also:
    • NO_SOURCE

      static final long NO_SOURCE
      sourceOfPacked(int, int, int) result meaning "no source tile".
      See Also:
    • SRC_TILE_X_SHIFT

      static final int SRC_TILE_X_SHIFT
      See Also:
    • SRC_TILE_Y_SHIFT

      static final int SRC_TILE_Y_SHIFT
      See Also:
    • SRC_PLANE_SHIFT

      static final int SRC_PLANE_SHIFT
      See Also:
    • SRC_ROTATION_SHIFT

      static final int SRC_ROTATION_SHIFT
      See Also:
    • SRC_TILE_MASK

      static final long SRC_TILE_MASK
      See Also:
    • SRC_PLANE_MASK

      static final long SRC_PLANE_MASK
      See Also:
    • SRC_ROTATION_MASK

      static final long SRC_ROTATION_MASK
      See Also:
    • STATIC

      static final DynamicRegion STATIC
      The scene nobody modelled as an instance: not an instance, no grid, no chunks. This is what GameSnapshot.dynamicRegion() answers by default, and it is the honest answer rather than a convenient one — sourceOf(int, int, int) on it is empty for every tile, which is exactly right for a static scene.

      An immutable constant, not a service instance.

    • STABLE_COPY_ATTEMPTS

      static final int STABLE_COPY_ATTEMPTS
      Copy attempts copyOfStable(GameSnapshot) makes before giving up. A copy takes microseconds against a ~20ms republish cadence, so losing three races in a row means something is wrong, not unlucky.
      See Also:
  • Method Details

    • isInstance

      boolean isInstance()
      Whether the scene is a dynamic region. When false the grid is empty and every sourceOf(int, int, int) is empty.
    • isTruncated

      boolean isTruncated()
      Whether the client's grid exceeded the wire cap and was dropped. The chunk array is then empty, but gridW(), gridH() and requiredChunks() stay populated so the overflow is diagnosable — a truncated region is indistinguishable from a static one on chunkCount() alone, which is why this flag exists.
    • sceneMode

      int sceneMode()
      The client's scene mode: SCENE_MODE_STATIC for a static scene, 4..7 for the dynamic size classes.

      Diagnostic only — isInstance() is the predicate. The two are independent fields on the client and can disagree: a never-written snapshot buffer reports mode 0, which is no mode at all, and a scene caught mid-rebuild can report a dynamic mode with no descriptor yet installed. The producer branches on the descriptor pointer, exactly as the engine does, and so should you. Use this for logging and for telling the size classes apart, never as the instance test.

    • originMapX

      int originMapX()
      Minimum loaded MAPSQUARE X — the grid's origin. Mapsquares, not chunks.
    • originMapY

      int originMapY()
      Minimum loaded MAPSQUARE Y — the grid's origin. Mapsquares, not chunks.
    • maxMapX

      int maxMapX()
      Maximum loaded MAPSQUARE X. Bounds the client's loaded window, not the resolvable area — see the class javadoc.
    • maxMapY

      int maxMapY()
      Maximum loaded MAPSQUARE Y. Bounds the client's loaded window, not the resolvable area — see the class javadoc.
    • gridW

      int gridW()
      Descriptor grid width in CHUNKS (0 when static).
    • gridH

      int gridH()
      Descriptor grid height in CHUNKS (0 when static).
    • requiredChunks

      int requiredChunks()
      4 * gridW * gridH as the producer computed it. Always written, so comparing it against chunkCount() tells you how badly a isTruncated() grid overflowed.
    • chunkCount

      int chunkCount()
      Number of descriptors actually published, 0 when static or truncated. Indices [0, chunkCount) are the only ones safe to read: the producer leaves the rest of the array stale rather than clearing it every tick.
    • chunkAt

      int chunkAt(int index)
      The raw packed descriptor at flat index index.

      The grid is plane-major: cell (gx, gy) on plane p is at ((p * gridW()) + gx) * gridH() + gy. Prefer chunkDescriptorAt(int, int, int), which does that arithmetic and the mapsquare-to-chunk conversion for you.

      Throws:
      IndexOutOfBoundsException - if index is outside [0, chunkCount())
    • chunkDescriptorAt

      default int chunkDescriptorAt(int tileX, int tileY, int plane)
      The raw packed descriptor covering world tile (tileX, tileY) on plane, or NO_CHUNK if that tile has no source chunk.

      This is where the UNITS TRAP is paid off exactly once: the tile is reduced to a chunk (>> 3) and the mapsquare origin is promoted to chunks (<< 3) before they are subtracted.

      Returns NO_CHUNK rather than throwing for every out-of-range case — not an instance, plane out of range, tile outside the grid, index past the published count. "No source" is a normal answer here.

    • sourceOfPacked

      default long sourceOfPacked(int tileX, int tileY, int plane)
      sourceOf(int, int, int) without the allocation: the source tile packed into a long, or NO_SOURCE.

      This is the hot path — a per-tile loop rebuilding an instance's collision runs this once per tile and must not generate garbage. Unpack with srcTileX(long) / srcTileY(long) / srcPlane(long) / srcRotation(long).

      Hoist GameSnapshot.dynamicRegion() out of the loop. This call allocates nothing, but acquiring the region is not free, and writing snapshot.dynamicRegion().sourceOfPacked(...) inside the loop puts the acquisition back on the per-tile path and throws away the reason this method exists:

        DynamicRegion region = snapshot.dynamicRegion();   // once
        for (int tileY = y0; tileY <= y1; tileY++) {
            for (int tileX = x0; tileX <= x1; tileX++) {
                long src = region.sourceOfPacked(tileX, tileY, plane);
                if (src != NO_SOURCE) {
                    bake(DynamicRegion.srcTileX(src), DynamicRegion.srcTileY(src));
                }
            }
        }
      
    • sourceOf

      default Optional<SourceTile> sourceOf(int tileX, int tileY, int plane)
      The static-map tile that world tile (tileX, tileY, plane) was copied from, or empty if this tile has no source — because the scene is static, because the tile is outside the descriptor grid, or because the instance has a hole there.

      Empty is a routine answer, not an error. In particular a tile can sit comfortably inside the client's loaded window and still have no source (see the class javadoc).

    • isHole

      static boolean isHole(int descriptor)
      Whether descriptor means "no source chunk". Tests < 0 rather than == NO_CHUNK so a malformed high-bit value degrades to "no source" instead of decoding into garbage coordinates.
    • descPlane

      static int descPlane(int descriptor)
      Source plane, 0..3. Meaningless on a hole.
    • descChunkX

      static int descChunkX(int descriptor)
      Source chunk X, 0..1023. Multiply by CHUNK_TILES for tiles, or >> 3 for the source mapsquare.
    • descChunkY

      static int descChunkY(int descriptor)
      Source chunk Y, 0..2047.
    • descRotation

      static int descRotation(int descriptor)
      Chunk rotation, 0..3 in 90-degree steps. Never observed non-zero live — see the class javadoc.
    • rotateLocalX

      static int rotateLocalX(int localX, int localY, int rotation)
      Source-local X for destination-local (localX, localY) within an 8x8 chunk rotated by rotation.

      Transcribed from the client's own rotation switch: r0 (x,y), r1 (y, 7-x), r2 (7-x, 7-y), r3 (7-y, x). Only the low two bits of rotation are used, matching the 2-bit wire field.

    • rotateLocalY

      static int rotateLocalY(int localX, int localY, int rotation)
      Source-local Y counterpart of rotateLocalX(int, int, int).
    • rotateLocRotation

      static int rotateLocRotation(int locRotation, int chunkRotation)
      A Location's rotation as it appears in the instance, given its rotation in the source region and the chunk's rotation: the two compose additively mod 4.

      Directional collision bits rotate by the same amount. Reading a source tile's wall flags and applying them unrotated is the standard way to get an instance's collision subtly wrong.

    • packSource

      static long packSource(int tileX, int tileY, int plane, int rotation)
      Packs a resolved source tile the way sourceOfPacked(int, int, int) returns it.
    • srcTileX

      static int srcTileX(long packed)
      Source tile X out of a sourceOfPacked(int, int, int) result.
    • srcTileY

      static int srcTileY(long packed)
      Source tile Y out of a sourceOfPacked(int, int, int) result.
    • srcPlane

      static int srcPlane(long packed)
      Source plane out of a sourceOfPacked(int, int, int) result.
    • srcRotation

      static int srcRotation(long packed)
      Chunk rotation out of a sourceOfPacked(int, int, int) result.
    • copyOf

      static DynamicRegion copyOf(DynamicRegion source)
      A heap copy of source, safe to retain across ticks.

      The snapshot-backed implementation is a flyweight over shared memory and dies with its tick. Rebuilding instanced collision is a multi-tick job, so it starts by copying the grid once here and keying the result on GameSnapshot.sceneVersion() — when that changes, throw the copy away and take a new one.

      Lossless: every scalar survives, including on a static or truncated region, so a copy is always a faithful record of what the producer published.

      Not torn-read protected. Every other snapshot accessor reads a handful of bytes; this one reads up to 64 KB out of a live, double-buffered mapping that the producer may overwrite mid-copy, and the region carries no seqlock to notice. The copy can therefore straddle two publishes and mix chunks from two different scenes — which for this data is worse than useless, because a half-stale grid resolves to plausible-looking wrong tiles rather than failing. The caller's obligation is to re-read GameSnapshot.publishSeq() after copying and retry if it moved. copyOfStable(GameSnapshot) does exactly that, and is what you should call unless you have a reason not to.

    • copyOfStable

      static Optional<DynamicRegion> copyOfStable(GameSnapshot snapshot)
      copyOf(DynamicRegion) with the torn-read check built in: copies the grid, re-reads GameSnapshot.publishSeq(), and retries if the producer republished while the copy was in flight.

      Returns empty if it lost the race 3 times. Empty means "ask again next tick" — it does not mean the scene is static, and substituting STATIC for it would be exactly the silent wrong answer this method exists to prevent.

      Snapshot stubs whose publishSeq() never moves always succeed on the first attempt, so this is safe to call from test code.