Interface DynamicRegion
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
- UNITS TRAP.
originMapX()andmaxMapX()are mapsquares (64 tiles);gridW()andgridH()are chunks (8 tiles). One mapsquare is 8 chunks. The conversion happens exactly once, insidechunkDescriptorAt(int, int, int)— callers pass world tiles and never do it themselves. 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 emptysourceOf(int, int, int)for a tile you can see is normal, not a bug.- 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 == 0throughout.rotateLocalX(int, int, int),rotateLocalY(int, int, int)androtateLocRotation(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 Summary
FieldsModifier and TypeFieldDescriptionstatic final intHighest local coordinate inside a chunk — the pivot the rotation math reflects around, as inCHUNK_MAX_LOCAL - localX.static final inttileX >> CHUNK_SHIFTis the chunk X containing that tile.static final inttileX & CHUNK_TILE_MASKis the tile's position within its chunk.static final intTiles along one edge of a chunk.static final intstatic final intstatic final intstatic final intstatic final intstatic final intstatic final intstatic final intstatic final intmapX << MAPSQUARE_CHUNK_SHIFTconverts a mapsquare to its first chunk — the x8 in the UNITS TRAP above.static final intHighest valid rotation — the inclusive bound for a range check.static final intDescriptor value meaning "this grid cell has no source chunk".static final longsourceOfPacked(int, int, int)result meaning "no source tile".static final intPlanes in a scene, and the outer dimension of the descriptor grid.static final intRotations are0..3in 90-degree steps.static final intComposing two rotations wraps with this mask.static final intsceneMode()value for an ordinary, non-instanced scene.static final longstatic final intstatic final longstatic final intstatic final longstatic final intstatic final intstatic final intCopy attemptscopyOfStable(GameSnapshot)makes before giving up.static final DynamicRegionThe scene nobody modelled as an instance: not an instance, no grid, no chunks. -
Method Summary
Modifier and TypeMethodDescriptionintchunkAt(int index) The raw packed descriptor at flat indexindex.intNumber of descriptors actually published,0when static or truncated.default intchunkDescriptorAt(int tileX, int tileY, int plane) The raw packed descriptor covering world tile(tileX, tileY)onplane, orNO_CHUNKif that tile has no source chunk.static DynamicRegioncopyOf(DynamicRegion source) A heap copy ofsource, safe to retain across ticks.static Optional<DynamicRegion> copyOfStable(GameSnapshot snapshot) copyOf(DynamicRegion)with the torn-read check built in: copies the grid, re-readsGameSnapshot.publishSeq(), and retries if the producer republished while the copy was in flight.static intdescChunkX(int descriptor) Source chunk X,0..1023.static intdescChunkY(int descriptor) Source chunk Y,0..2047.static intdescPlane(int descriptor) Source plane,0..3.static intdescRotation(int descriptor) Chunk rotation,0..3in 90-degree steps.intgridH()Descriptor grid height in CHUNKS (0when static).intgridW()Descriptor grid width in CHUNKS (0when static).static booleanisHole(int descriptor) Whetherdescriptormeans "no source chunk".booleanWhether the scene is a dynamic region.booleanWhether the client's grid exceeded the wire cap and was dropped.intmaxMapX()Maximum loaded MAPSQUARE X.intmaxMapY()Maximum loaded MAPSQUARE Y.intMinimum loaded MAPSQUARE X — the grid's origin.intMinimum loaded MAPSQUARE Y — the grid's origin.static longpackSource(int tileX, int tileY, int plane, int rotation) Packs a resolved source tile the waysourceOfPacked(int, int, int)returns it.int4 * gridW * gridHas the producer computed it.static introtateLocalX(int localX, int localY, int rotation) Source-local X for destination-local(localX, localY)within an 8x8 chunk rotated byrotation.static introtateLocalY(int localX, int localY, int rotation) Source-local Y counterpart ofrotateLocalX(int, int, int).static introtateLocRotation(int locRotation, int chunkRotation) ALocation'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.intThe client's scene mode:SCENE_MODE_STATICfor a static scene,4..7for the dynamic size classes.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.default longsourceOfPacked(int tileX, int tileY, int plane) static intsrcPlane(long packed) Source plane out of asourceOfPacked(int, int, int)result.static intsrcRotation(long packed) Chunk rotation out of asourceOfPacked(int, int, int)result.static intsrcTileX(long packed) Source tile X out of asourceOfPacked(int, int, int)result.static intsrcTileY(long packed) Source tile Y out of asourceOfPacked(int, int, int)result.
-
Field Details
-
PLANE_COUNT
static final int PLANE_COUNTPlanes in a scene, and the outer dimension of the descriptor grid.- See Also:
-
CHUNK_TILES
static final int CHUNK_TILESTiles along one edge of a chunk. The unit the whole descriptor table is in.- See Also:
-
CHUNK_SHIFT
static final int CHUNK_SHIFTtileX >> CHUNK_SHIFTis the chunk X containing that tile.- See Also:
-
CHUNK_TILE_MASK
static final int CHUNK_TILE_MASKtileX & CHUNK_TILE_MASKis 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_LOCALHighest local coordinate inside a chunk — the pivot the rotation math reflects around, as inCHUNK_MAX_LOCAL - localX. Numerically equal toCHUNK_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_SHIFTmapX << MAPSQUARE_CHUNK_SHIFTconverts a mapsquare to its first chunk — the x8 in the UNITS TRAP above.- See Also:
-
ROTATION_COUNT
static final int ROTATION_COUNTRotations are0..3in 90-degree steps.- See Also:
-
MAX_ROTATION
static final int MAX_ROTATIONHighest valid rotation — the inclusive bound for a range check.- See Also:
-
ROTATION_MASK
static final int ROTATION_MASKComposing two rotations wraps with this mask. Numerically equal toMAX_ROTATION; a mask is not a bound, so don't swap them.- See Also:
-
SCENE_MODE_STATIC
static final int SCENE_MODE_STATICsceneMode()value for an ordinary, non-instanced scene.- See Also:
-
NO_CHUNK
static final int NO_CHUNKDescriptor 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_SOURCEsourceOfPacked(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
The scene nobody modelled as an instance: not an instance, no grid, no chunks. This is whatGameSnapshot.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_ATTEMPTSCopy attemptscopyOfStable(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. Whenfalsethe grid is empty and everysourceOf(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, butgridW(),gridH()andrequiredChunks()stay populated so the overflow is diagnosable — a truncated region is indistinguishable from a static one onchunkCount()alone, which is why this flag exists. -
sceneMode
int sceneMode()The client's scene mode:SCENE_MODE_STATICfor a static scene,4..7for 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 mode0, 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 (0when static). -
gridH
int gridH()Descriptor grid height in CHUNKS (0when static). -
requiredChunks
int requiredChunks()4 * gridW * gridHas the producer computed it. Always written, so comparing it againstchunkCount()tells you how badly aisTruncated()grid overflowed. -
chunkCount
int chunkCount()Number of descriptors actually published,0when 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 indexindex.The grid is plane-major: cell
(gx, gy)on planepis at((p * gridW()) + gx) * gridH() + gy. PreferchunkDescriptorAt(int, int, int), which does that arithmetic and the mapsquare-to-chunk conversion for you.- Throws:
IndexOutOfBoundsException- ifindexis outside[0, chunkCount())
-
chunkDescriptorAt
default int chunkDescriptorAt(int tileX, int tileY, int plane) The raw packed descriptor covering world tile(tileX, tileY)onplane, orNO_CHUNKif 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_CHUNKrather 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 along, orNO_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 writingsnapshot.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
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) Whetherdescriptormeans "no source chunk". Tests< 0rather than== NO_CHUNKso 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) -
descChunkY
static int descChunkY(int descriptor) Source chunk Y,0..2047. -
descRotation
static int descRotation(int descriptor) Chunk rotation,0..3in 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 byrotation.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 ofrotationare used, matching the 2-bit wire field. -
rotateLocalY
static int rotateLocalY(int localX, int localY, int rotation) Source-local Y counterpart ofrotateLocalX(int, int, int). -
rotateLocRotation
static int rotateLocRotation(int locRotation, int chunkRotation) ALocation'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 waysourceOfPacked(int, int, int)returns it. -
srcTileX
static int srcTileX(long packed) Source tile X out of asourceOfPacked(int, int, int)result. -
srcTileY
static int srcTileY(long packed) Source tile Y out of asourceOfPacked(int, int, int)result. -
srcPlane
static int srcPlane(long packed) Source plane out of asourceOfPacked(int, int, int)result. -
srcRotation
static int srcRotation(long packed) Chunk rotation out of asourceOfPacked(int, int, int)result. -
copyOf
A heap copy ofsource, 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
copyOf(DynamicRegion)with the torn-read check built in: copies the grid, re-readsGameSnapshot.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
STATICfor 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.
-