The profile keeps every cosmetic it has seen, a removed mod's too, and refused to save past
8192 of them (or of Build objects). A player who had tried enough mods passed that, every save
was refused, and the cosmetic catalog failed with it: no items in the customization menu and
no playcard. The saved limit is now 65536 (saved_item_limit), a bound against a damaged file;
the game's own catalog of installed items keeps its 8192.
Also: one item the catalog cannot hold (two items with one identity hash, a record the game
will not read) is left out and named in the log instead of failing the whole catalog, and a
catalog that still fails logs why.
Changing a setting in the game's menu did not stick. The camera went
back to high on leaving the menu, grind assist and lean exit stayed at
20 / on, the compass and scoring ticker kept their old state, and the
Experimental tab's "reset to default" returned to whatever was set at
launch.
b763631 gave the settings hooks per-thread caches of saved values that
look a value up again only when profile::Store::changes() advances.
Only whole-profile commits advanced it. Every single save (a setting,
an option, an outfit) goes through Store::Update::commit(), which did
not, so the caches answered with launch-time values for the rest of
the session. native_setting_get also wrote the cached value back over
the new one when the two differed. The saves themselves reached the
database, so profiles are intact.
- Update::commit() advances the counter through Store::note_change(),
after value_ holds the new values and before the lock is released.
Store::commit() and the constructor use it too.
- A cache hit needs the whole key text, not the key's address and first
8 bytes. Many keys share those bytes (egf_enable..., egf_exp_...,
CRAS_HasSeen...Tab), so a reused address could answer for another
setting. Still no heap string, store lock or map walk on a hit.
- An outfit that is not loaded or not saved is logged as a warning with
its reason and preset (local_cosmetic_loadout_rejected). It was a
debug event, so a player's log never said why an outfit was refused.
Routine cases stay at debug.
- dingosdk_profile_change_tests (with the park editor tests): each kind
of save advances changes(), an unchanged save does not, and saved
values keep their types across a restart.
Mesh streaming pool (fixes loading crashes on older GPUs)
- 1.0.0 forced MeshStreaming.PoolSize to 3.5 GiB on every card. An RX 580
crashed while loading with DXGI_ERROR_DEVICE_RESET, and the oversized pool
is the likely cause. The pool is now a quarter of the largest hardware
adapter's dedicated video memory, capped at 3.5 GiB; cards of 6 GB or less
keep the game's own 520 MB. The card, its memory and the chosen size are
logged once at startup.
Remote players (busy servers)
- Transform interpolate is inline plain arithmetic: 22 -> 5.8 ns per joint.
It was 4% of a busy server's client frame, since every player within
200 m has the whole skeleton interpolated every frame.
- Players out of the camera's view and more than 8 m away are sampled at
20 Hz, like far players. Their position still updates every frame for
collision, sound and nametags, and a player coming into view is sampled
that frame.
Expression hooks (run for every expression the game executes)
- No profiler zones in the expression hooks: taking a sample switched them
on, inflating the very samples that measured the hooks.
- The throwdown lab pump does one sorted lookup of the running graph and
skips everything else for graphs it doesn't watch (graph_throttle gets
throttled_graph for this).
Settings and memory reads
- Script gameplay-setting and native-setting gets are served from small
per-thread caches, keyed on the asset/key address, its first 8 name
bytes and a new profile::Store::changes() counter, instead of a store
lookup each call.
- Per-frame reads in the client tick, native settings and population
controls use memory::peek instead of ReadProcessMemory.
- Script error logging probes each argument pointer once.
Launcher
- "Close the launcher when Skate starts" now hides the launcher and exits
when Skate.exe does, instead of exiting as the splash opens. Outside a
Steam library, Skate's protection closed the game (TerminateProcess 0x32)
when the launcher exited during startup; players confirmed turning the
setting off avoided it.
- STATUS tile warns when Steam isn't open or signed in that PLAY starts
offline as Unknown Player (hidden when Offline mode is chosen or while
launching).
- Setting description updated to match.
Startup interventions (20260929)
- exit_return 0x87755f2 -> 0x87fe7fb: the last LEA RSP,[RSP]; JMP stub
before the PUSHFQ / LOCK CMPXCHG routine, as 0x87367cf was in 20260908,
and the return address player logs show. Only enforced once a frame site
is armed; this build's protector has no equivalent rep stosb frame clear.
Client frame cost (profiled 2026-10-02)
- Transform interpolate is inline plain arithmetic (~1% of an MP frame).
- Script gameplay-setting and native-setting gets are served from small
per-thread caches keyed on asset/key address, name prefix and a new
profile::Store::changes() counter, instead of a store lookup each call.
- Hot-path reads in client tick, native settings and population controls
use memory::peek instead of ReadProcessMemory.
- Script error logging probes each argument pointer once.