Runtime capacities and allocator layouts
The capacities below apply to Skate.exe build 20260929. ReSkate changes selected allocations and guards; content counts alone do not describe pressure when stock resident content and outgoing layers still occupy the same pool. Observed
| Pool or extent | Stock | ReSkate behaviour | Confidence |
|---|---|---|---|
| Streamed texture handles | 20,480 | 32,764 from 2.0.7 | Observed |
Cull ids per spatial queue (SpatialQueryManager.InitialCullIdListSize) |
40,000, allocated once and never grown | 262,144 in 2.1.0 | Sourced · ReSkate |
| Light ids per class | 8,192 in each of six classes | Guard refuses new light entities near capacity | Observed |
| Physics geometries/bodies | 5,000 / 8,200 | 40,000 / 65,000 from 1.1.7 | Sourced · ReSkate |
| Render-resource ids | 8,192 | 65,536 from 1.1.7 | Sourced · ReSkate |
Spatial records per cull-id owner (the array at owner +0x330) |
65,536, no capacity check | Unchanged in 2.1.2: it caps a level below the raised cull-id list | Observed |
| Entity slots | Not established here | 262,144 in 2.0.7; an older 524,288 log label is stale | Sourced · ReSkate |
| Renderer descriptor heap | 131,072 per frame | Raised by 2.1.0 | Sourced · ReSkate |
| Mesh streaming pool | 532,960 KB | Version-dependent sizing | Sourced · ReSkate |
| Spatial root | ±10,000 m (Core.WorldSize) |
Landings checked at x +8,000 and z −8,000 m | Observed |
The stock handle count is an immediate, mov r12d,0x5000 at Skate+0x3e72bd6; exceeding it logs
“Exceeded max streaming texture handle count”. Observed
The texture handle index has fifteen bits. The 32,764 allocation remains below the 32,767 numerical ceiling; raising texture resolution and raising handle count are different operations. No 256-pixel texture-side limit is established for skate. Observed
On a measured Studio-level load under the stock handle limit, roughly 10,400 handles remain for map textures, or around 9,000 with the inspected cosmetic mods. ReSkate’s texture-use counter is a session high-water mark, so it cannot measure current live residency after unloading a map. Observed
Cull-id free list
Section titled “Cull-id free list”One pool owner exists per level or world context; its constructor is Skate+0x489b9a0. The owner holds
0x800 records of 0x90 bytes at +0x70, a free list of u32 next links at +0x80 and a u32 head at +0xc0.
Empty head is 0xffffffff. Observed
Exactly three functions pop the list (+0x489cf90, +0x489db30, +0x489d400): they are every read of a head
at displacement 0xc0 in the module. The failing pop at Skate+0x489cfe9 indexes with the sentinel, reading
[array+0x3fffffffc], instead of rejecting exhaustion. If that read succeeds, the pop writes -1 to wild
addresses and hands out garbage ids, so corruption can precede any fault. Observed
The game’s GameView.cfg sets 80,000, but it is read only in GameView mode; ordinary play keeps 40,000.
Observed
One spatial/render object holds its id at +0x3c. Grouped instances are counted by member mesh, not instance.
Repeated skater creation due to missing spawn ground can exhaust ids independently of the map’s steady count.
Observed
The same owner holds a second, paged array at +0x330: 512 page pointers (+0x330 to +0x1330), the page
allocator at +0x1330 and a u32 count at +0x1338. A page holds 128 records of 0x30 bytes. The append
Skate+0x4eeff50 takes page count>>7, allocates a missing page, returns page + (count & 127) * 0x30 and
increments the count, without checking it against the 512 pages. Observed
The registration at Skate+0x4ebecc9 pops a cull id for an object that has none (Skate+0x489db30) and appends one
record to this array for it, so the array grows with the cull ids the owner hands out. It holds 65,536 records.
Appends at counts 65,536 to 65,663 take the allocator pointer at +0x1330 as page 512 and write their records over the
allocator object; the append at count 65,664 takes the qword at +0x1338 (the count, with the next u32 above it) as page 513.
Observed ReSkate’s patch tables at 2.1.2 change neighbouring functions’ page shifts but no instruction of
Skate+0x4eeff50. Sourced · ReSkate source
In one Studio level loaded under ReSkate 2.1.2, objects needing 100,777 cull ids (one per object per 200 m cell
the object’s box meets) faulted 1 s after the level became active. The fault was at Skate+0x4ebecf0, the write of
an appended record, through the page pointer 0x400010080 with a zero record offset: the count 0x10080 (65,664)
with 4 above it, read as page 513, as the overflow produces. The same level with 49,410 cull ids loaded. So the raised cull-id list does
not raise the level’s capacity: the record array still limits a level to 65,536 cull ids, less what stock resident
content in the same owner holds. Observed
Light tables
Section titled “Light tables”The six managers are OldPoint, OldSpot, PbrSphere, PbrSpot, PbrTube and PbrRectangular. Each has four selector
tables sharing its class allocator. Each selector has 64 page pointers, with 128 eight-byte entries per page.
The insert uses page=id>>7, entry=id&127 without a capacity check. Observed
The static constructor Skate+0x4eab1a0 builds the managers and stores each one’s name at manager +0x410;
the dispatcher +0x4ec5ee0 switches on light descriptor +8. Observed
| Descriptor type | Manager | Offset in the owner | Creator | Entity factory | Confidence |
|---|---|---|---|---|---|
| 0 | OldPoint | +0x60 |
+0x4f1b560 |
+0x52d3f50 |
Observed |
| 1 | OldSpot | +0x570 |
+0x4f1b600 |
+0x52d4480 |
Observed |
| 2 | PbrSphere | +0xa80 |
+0x4f1b740 |
+0x52d0e00 |
Observed |
| 3 | PbrSpot | +0xf90 |
+0x4f1b7e0 |
+0x52d1740 |
Observed |
| 4 | PbrTube | +0x14a0 |
+0x4f1b880 |
+0x52d1d40 |
Observed |
| 5 | PbrRectangular | +0x19b0 |
+0x4f1b6a0 |
+0x52d2680 |
Observed |
A selector table is built by +0x304a490; +0x304c2d0 always allocates exactly 64 pages of 0x400 bytes.
The class allocator +0x3e844f0 pops its free list at +0xe8 or atomically increments the issue counter at
+0xf0; the four selectors of a class share it. The insert is +0x4ea3ff0. Observed
Runtime memory; offsets are relative to one selector table.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 512 | pointer[64] | page pointers | Observed | |
| 0x200 | 8 | pointer | snapshot pointer | Observed | |
| 0x208 | 72 | bytes | unknown | Unknown |
Ids 8,192–8,319 can overwrite through the snapshot pointer before id 8,320 faults at Skate+0x4ea4094
(unwind +0x4ea4094 < +0x4f1b844 < +0x4ec5fbd < +0x1959d92 < +0x5304a7a).
Refusing a light in its per-kind entity factory avoids later unchecked renderer setters; returning handle zero
after insertion has failed does not. Observed
Allocator records are 0x60 bytes. For id i, page is bsr(i+16)-4, its pointer at allocator +8+8*page,
and index is (i+16)-2^bsr(i+16); the record lookup is Skate+0x604af0. The next free id is at +0x50. Live
count is issued count at allocator +0xf0 minus free-list length. Observed
A light’s creation information carries no owner that would attribute it to a layer: +0x140 is the per-light
transform, +0x158 the creation batch, and +0x10/+0x90 are pooled. Observed
ParamDb lifetime
Section titled “ParamDb lifetime”Runtime memory; remaining ranges are not interpreted here.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 8 | u64 | reference count region | Observed | |
| 0x8 | 8 | pointer | allocator | Observed | |
| 0x18 | 8 | pointer | binding table | Observed | |
| 0x48 | 4 | u32 | type region | Observed | |
| 0x4E | 2 | u16 | member count | Observed | |
| 0x58 | variable | pointers | payload pointers | Observed |
The owner’s 40-byte payload block begins with the word (8 << 48) | owner. Observed
The binding table at owner +0x18 reaches its records through a page-pointer directory. The 64-bit word at
table +8 packs the directory pointer in its low 48 bits, the directory length in the next 15 bits and a growth
lock in the top bit. +0x3cbb6c0 grows the directory on demand, checking the page index against the packed
length at +0x3cbb6ef. Inferred
Page j holds 2^(j+1) records of 0x30 bytes, so record n lives in page floor(log2(n+2)) - 1 at index
n + 2 - 2^(j+1); callers derive n from per-owner 32-bit counters. These pages grow without a fixed capacity,
unlike the 64 × 128 light tables. Inferred
+0x3cb5a30 creates an owner through the constructor +0x3cb5dc0, which always makes a new owner rather
than reusing one. Observed
The owner and its payload are freed synchronously the moment the reference count reaches zero. About 680
open-coded call sites decrement and free in place (251 through +0x3cba570, 419 through the value-box thunk
+0x3cba640, others through +0x3cba7a0 and +0x3cb4d00), with no lock and no deferred phase. Observed
GlobalSBA’s free path (+0x15a5c20) writes the previous free block into the freed allocation’s first qword as a
per-thread LIFO link. Consequently a stale reference can appear as a plausible allocator pointer: a block whose
first qword points at another free block of the same size has itself been freed. Observed
ParamDb queue phases
Section titled “ParamDb queue phases”Each ParamDb manager has a job queue. +0x3cbcfe0 reserves it, enqueuing a marker only when the queue is
empty; +0x3cbea30 releases and drains it; the enqueue +0x18b6180 returns true only for an empty queue.
Observed
| Job descriptor | Job | Function | Confidence |
|---|---|---|---|
+0x7181208 |
Walker | +0x3cbcd30 |
Observed |
+0x7181280 |
Batch walk | +0x3cbabd0 |
Observed |
+0x7181230 |
Destroy | +0x3cb37a0 |
Observed |
+0x7181258 |
Marker | Not named | Observed |
The walker holds no references to the owners it visits, and the creator +0x3cb9d00 publishes an owner before
taking references to it; both are safe only inside a queue phase. Observed The scoped arenas +0x4898f70,
+0x4899ae0 and +0x48994c0 are per-call scratch. Old binding-table page directories retire through a
four-slot epoch ring at +0x771f470, drained by +0x3f7bf10. Observed
Depot key registry
Section titled “Depot key registry”Loaded ShaderBlockDepots register each material key in a hash table of ParamDb value boxes. The depot holds the
bucket array pointer at +0x1d0 and the bucket count at +0x1d8; buckets[count] is a sentinel. The insert
helper +0xfa55d0, called at +0x46a00b4, returns the node, its bucket, and an inserted flag at result +16.
Observed
Runtime memory.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 8 | u64 | material key | Observed | |
| 0x8 | 8 | pointer | value box handle | Observed | Points at the box's 64-bit reference count |
| 0x10 | 4 | u32 | registration count | Observed | |
| 0x14 | 4 | bytes | unknown | Unknown | |
| 0x18 | 8 | pointer | next node | Observed |
The handle points at the reference count, eight bytes into the box; the release thunk +0x3cba640 subtracts
eight and continues at +0x3cba7a0. During one measured load of a level whose depots register different blocks
under one key, a key was registered twice with a box other than the node’s. Observed
General free dispatch
Section titled “General free dispatch”+0x1581010 frees with an explicit arena, pointer and size; +0x157dae0 frees by pointer alone. Its type-7 case
(jump table at +0x15812a4) calls +0x159c970, which resolves the actual arena and re-enters +0x1581010 with
the same pointer and size at +0x159c9ab. A hook on that function therefore records two frees for one logical
free; the nested one is not a second allocation. Observed
The arena object at +0x7319ad0 is GlobalSBA, initialised by +0x1599220. Observed
GPU and settings diagnostics
Section titled “GPU and settings diagnostics”The DX12 backend is linked into Skate.exe; D3D12CreateDevice is imported through sl.interposer.dll, and the
game loads the Agility runtime from x64/D3D12Core.dll. Missing that runtime produces error 0x887e0003.
Observed
GPU-hang reports include an RC2 breadcrumb tree, the game’s own DRED dump, camera and memory information. They
are plain protobuf files named eadp.errorsdata.<UTC>_gpu.st.dat in eadp-sdk/persistence/PROD of the game’s
Electronic Arts application-data folder, and the same text is emitted through OutputDebugString. In eight of nine
recorded hangs the breadcrumb sits in GBuffer_HighEnd > mainGBufferEs. Code resides
in executable .xtls/.tls$ sections as well as ordinary PE structures, so scanning only .text misses relevant
functions. Observed
Loose config/Game.cfg is read at startup; a line applySettings [=[ Name value ]=] appended to it sets an
engine setting for the run. The settings catalogue (6,602 settings) can expose values that exist but have no
visible effect: PerfOverlay enable/FPS settings read back as one without drawing in this build, and
FovMultiplier changes nothing in the frame. Mesh.ForceLod 1 or 2 does force mesh LODs. Observed
An unfocused game window presents at about 23 frames per second wherever the camera is, so frame-rate measurements need a focused window. Observed
Frame rate also falls over a session. Players report a decline from 150 frames per second at half GPU load to about 20 at full load within one session, and custom maps dropping to about 10–40 frames per second after another map is loaded in game, which stock maps do not; only restarting the game restores it. Sourced · ReSkate community reports Frame-rate comparisons therefore start from a fresh launch. Inferred
Settings values
Section titled “Settings values”Live values read at the main menu at the Ultra quality tier:
| Setting | Value | Confidence |
|---|---|---|
WorldPartition.AgentFloatAttribute00–03 (fullagent long/medium/short/detail simulation radius) |
230 / 130 / 80 / 60 m | Observed |
WorldPartition.AgentFloatAttribute04, 05 (long/medium vista radius) |
10,000 / 10,000 m | Observed |
WorldRender.DetailCullDistance, MediumCullDistance, LongCullDistance, ExtraLongCullDistance |
60 / 130 / 230 / 400 m | Observed |
WorldRender.CullScreenAreaScale |
1.75 | Observed |
WorldRender.ShadowmapViewDistance |
250 | Observed |
Render.ViewDistance |
20,000 | Observed |
Mesh.GlobalLodScale, Mesh.ForceLod, Mesh.RuntimeLodQualityLevel |
1, −1, 3; no LodScale setting exists |
Observed |
MeshStreaming.PoolMaxAllocCount, MaxPendingLoadCount |
2,048, 4 | Observed |
MeshStreaming.ReservedPositionedInstanceCount |
50,000; a written 200,000 is applied | Observed |
MeshStreaming.ReservedDistancedInstanceCount |
128 | Observed |
GpuRp.MaxVisibleMeshlets |
1,048,576 | Observed |
TextureStreaming.PoolSize |
1,441,040 | Observed |
Placed-object cull defaults are Near 70 m, Far 200 m, FarSp 350 m and AreaFactor 0.038; values written to them
take effect and survive a level load. Observed MeshStreamingSettings also carries debug switches
(DumpInstanceList, TraceMeshInstanceCounts, DumpPoolAllocations) and Sweepable* pool settings, whose
effect is not measured. Observed