The renderer gives each frame 131,072 shader-visible descriptors past a persistent 120,000, in
heaps of 333,333. Dense custom maps (the Los Santos City sublevels, San Andreas) draw more than
that in a frame, and the renderer wrote past the end of the heap: shaders read garbage
descriptors, which faulted or hung the GPU (page faults at nonsense addresses, in draws, texture
fetches and ray tracing alike). ReSkate now patches the device setup at boot so each heap is
D3D12's 1,000,000 and a frame may use 524,288; heaps are 644,288 descriptors. LS-B2, LS-B3 and
San Andreas (210,000 a frame at its peak) now hold.
Also in this change:
- Memory: perf memory owners/heap/gpu/report, a ledger of who commits the game's RAM; the
render-resource pool no longer allocates its 115 MB twice; the EadpErrorsData arena is
capped at 32 MiB (EA's error reporting is blocked anyway).
- Crash reports: tagged with the map, destination, graphics card and driver version (protocol
v3), so Sentry can group GPU hangs by map and card.
- GPU diagnostics (--gpu-diagnostics): a fuller DRED report, the device-removal and video
memory watches, a large-draw watch, a record of which game code made and freed each GPU heap
and resource, checks of every GPU address bound, and a meter of each descriptor heap's use.
- World pools: peak use of entity pages, render instances and spatial blocks in the log.
- dingosdk_mod_mesh_check: checks every MeshSet a mod ships against its geometry; the MeshSet
reader now knows static meshes' layout.
Boards wear: hard landings, hits onto rails and ledges, grinding and
slams wear the board of the deck in the game, kept per deck. From a fifth
worn it steers lazier and its trucks go soft (the game's own physics
tuning, outside multiplayer sessions only); a worn board breaks from less
and, worn out, can give way under the rider. A board that broke is a new
one once it is whole again, and the game's own board wear is cleared with
it.
A boardslide, lipslide or darkslide that comes down too hard onto a rail
can snap the board, and the rider goes down with it: No Bail can now be
asked for a wipeout, which goes in as a cause before the selector picks.
Sounds play through XAudio2: a crack by the size of the break, metal when
the board broke on metal (the material comes from the game's contact
effects; which are metal is marked in play or listed by a pack), and the
body's hits in a bail by how hard they land.
The settings are a card on the overlay's Skater page, two rows in Mod
Options and console verbs, kept in BoardBreak.settings. A deck may name
itself and set its wheels wider apart in a deck.ini; Old School and Pig
join the decks the feature knows.
he export dialog's quality setting picks 8, 12 or 18 Mbps for the H.264
encoder (three mov eax immediates at Skate.exe+0x62c24f, +0x62c256 and
+0x62c25d in the 20260929 build). All three are now 100000 kbps.
- The code around them must match the supported build's bytes, and each
value must be the game's own or already 100000; otherwise nothing is
written and the log says the change was skipped.
- The log says whether the bitrate change or the frame-pacing hook was
skipped. start() reports success if either one is in place, so
replay_export_initialized can say active while the hook is off.
- The fix that encodes every rendered frame is unchanged.
The game keeps each kind of light (PbrSpotLight, PbrSphereLight, ...) in a
table of 8,192 and crashes at Skate.exe+0x4ea4094 when a level makes more.
- Each kind's light factory is hooked: once the kind's table is nearly full
it gives no entity, as the game's own factory does for a light it should
not make. The light is not drawn and the game goes on.
- Said in the log: the kind of light and how many were left out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the mods are read on several threads (0ae0e71), read_mod reads every
script a mod replaces: whether a higher mod's version stands is only known
once the mods are settled. Before, a script a higher mod had replaced was
never read. When such a read fails, the TOC's catch noted it and gave up the
rest of that TOC, so the mod's later changes and additions were left out of
other mods' copies, with a note blaming the mod where nothing used to be read.
The read now has a try of its own. A failure is kept with the script and
settled with the rest: where a higher mod's version stands it goes, note and
all, as if never read; where the script is wanted, the note stands and only
that script is missing, the rest of the TOC having been read on.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VNYF3aYa2q3MkFXc5xYnDd
2.0.8's dedicated server was released with a world-layers.json scanned from
a game Steam had updated: 263 layers, where every player's game has 261. A
server that shares its layers (world_layer_sync) sends one choice for each,
the player's game turned the message down as unreadable, and nobody could
join such a server ("Join code, sender identity, or multiplayer protocol
did not match", and "Disconnected." in the server's log).
- supported_build::world_layer_count (261): how many layers the supported
build's levels have.
- Server: a world-layers.json of another count is not used; the log says
why and layer sync stays off, where before every player was turned away.
- --export-world-layers refuses a game folder whose count differs, so a
release cannot be built from an updated game again.
- Player's game: a layer list of another length is left unread and every
layer stays as the player has it; the rest of the message is taken and
they join.
Shader tables
- Reverts a61f82c ("Shader crash fix"). Since 2.0.8 a mod's own shader
programs were copied into the merged table, and the tables that cosmetic
bundles carry were merged across mods. Each such bundle then held other
mods' programs, and the game built them as the bundle loaded, before the
files with their compiled variants were there. That was 2.0.8's most
reported crash (Skate.exe+0x3f86da6, some forty machines in ten minutes).
2.0.9's guard passed such a program over, and the same players then
crashed a little later in cosmetic loading (+0x3f7bb65, +0x16fe1a6,
+0x3dc07ed).
- Whatever crash a61f82c fixed comes back with this for the mods that
needed it.
- The guard stays.
Music
- The game's read of one song for its scorer (0x14f23a0) is replaced by the
same read with the check it lacks: a place past the end of the playlist
asset's songs has no song, which the game already answers with 0. 2.0.9's
check in music_select_next_hook looked at what the queue held before the
game was asked and did not see these; the game asked for places such as
0xd570 and crashed reading them (7 reports on 6 machines).
- Said in the log the first eight times.
Not confirmed in game: neither change has been run by a player who was
crashing.
Music screen: large playlists no longer empty it, and two logs say why things fail
Music screen
- The screen's list was turned down whole, and came up empty (the game's own
Featured row too), once it held more than 1,024 songs: a few large YouTube
Music, Spotify or local playlists went over that. The most is now 2,000
songs and 16,384 places in playlists (music_ui_most_songs,
music_ui_most_places); 2,000 because the screen's own table of songs is
read only up to 2,048.
- The rows of the player's playlists are counted as they are registered and
held under that: with no room left, the rest of a playlist is left out of
its list (it still plays) and the screen is never empty for it.
- A row whose "artist - title" is longer than the screen takes is left out;
one such row turned the whole list down as well.
- Said in the log: how many songs were left unlisted, and, should the list
still be turned down, what it held.
Logs
- Spotify device: librespot's own warnings and errors are written at the
warning level again; only the rest of what it says stays at debug. A
player whose Spotify connects and stays silent had nothing to send.
- Multiplayer: a message turned down with "Join code, sender identity, or
multiplayer protocol did not match" now says which of the three it was,
its size and first bytes, and whether it came from a dedicated server.
After Steam updated skate., some installs had the supported Skate.exe over
the new build's data (a copied game, or a download that stopped part-way).
The launcher called that ready, every mod was left out as "made for game
build 29544144, not the installed 29663206; rebuild it with ReSkate Studio",
and the mods stayed out after the game files were put right.
Launcher
- Ready now also needs the game's data to be the supported build, read from
Data\layout.toc against supported_build::game_data_changelist (29544144).
Data of another build, or none, reads "Game files need verifying".
- DOWNLOAD in that state checks every file (-validate) instead of fetching
only what is missing. A download that fails part-way no longer ends on
ready because Skate.exe arrived.
- Data whose build cannot be read does not block.
Mods
- A mod left out because the game was of another build is no longer
remembered in .reskate-excluded.json: it is checked on every launch. One
already remembered for it is looked at again.
- When it is the game that is not the supported build, the mod's message
says to verify the game files in the launcher, not to rebuild the mod.
Objects other players place are spawned locally, so the game took every one
for the local player's own: all were solid, and their card offered the
owner's buttons and named the wrong player.
- Other players' objects are ghosts the skater passes through, as in the
game's own sessions. Each player's row in the session menu has an
"Objects: solid / ghost" switch, remembered by Steam ID; a dedicated
server's own objects (its parks) are solid unless switched off.
`mp objects-solid <SteamID64|server> on|off` does the same.
- The card's own Disable and Enable on one object stand over that player's
setting until the setting is changed.
- The card on an object someone else placed no longer offers Edit or
Delete: the game's "is this mine" and "may this be edited" checks answer
for who really placed it. It offers Disable instead.
- The card names the owner of the object it is for, read from the card's
own script, instead of the owner of the object nearest the skater.
- A dedicated server has a player card of its own, "Server", shown on what
it placed.
Server: the Mods folder is kept when the configuration is reloaded. A park
mod was reported as not being in the Mods folder after a reload.
A Jam Session takes the score of the line just landed from the score HUD's
text, with the script native ParseInt, which is the C library's atoi. atoi
stops at the first character that is not a digit, so a line shown as 5,432
was read as 5 and added 5 to the player's total. Reported for Jam Sessions
and Spot Battles: lines of 5,000 and more adding one or two points.
ParseInt now reads groups of three digits whole, set apart by a comma, a
full stop, an apostrophe or a space: "5,432" is 5432. Text that is not a
grouped number is read exactly as before. The first few grouped scores are
logged, to confirm the cause in play.
Crash dumps now go to the project's own Sentry server. The launcher's
out-of-process helper is unchanged; only where it posts and what it expects
back are:
- The default endpoint is the Sentry project's minidump URL. Report
attributes travel as Sentry tags, with the release as reskate@<version>.
- An upload counts as accepted when the server answers with an event ID.
- "backtrace" is "crash_report" throughout: the namespace, files, CMake
targets, DINGOSDK_CRASH_REPORT_URL, DINGOSDK_BUILD_CRASH_REPORT_TESTS and
the RESKATE_CRASH_REPORT_URL override.
Performance timings are new, and off unless "Send performance timings" is
ticked in the launcher's settings (RESKATE_TRACING=1). A small tracer
(Engine/Core/Debug/trace.h) posts each finished step to Sentry as a
transaction from a background thread; the launcher and the game it starts
share one trace. Timed: the update check, the Steam download, Thunderstore
installs, the mod merge, starting the game, the launch up to the first
playable level, each level load, joining a session, fetching the identity
lists, and the time spent in each level with its average frame rate and
long frames. Turning crash reports off turns timings off too.
Sources
- Local Files: add a folder of music from the setup page (MP3, M4A, AAC, Ogg,
FLAC, WAV; up to 200 songs a folder, 12 folders). Names and covers come from
the files' tags. Each folder is a tile in its own row.
- Online Radio: find stations by name (radio-browser.info) or add a stream's
address (MP3, AAC, .pls, .m3u; no HLS). Each station is a tile in its own
row; the track name follows what the station sends. Up to 24.
- Both are decoded by the library built into the DLL (SpotifyHelper), which
now plays files and HTTP streams as well as a program's output, reads tags
and embedded pictures, and accepts MPEG-2 ADTS from AAC stations.
Sound
- Five-band equaliser (60 Hz, 250 Hz, 1 kHz, 4 kHz, 12 kHz) and loudness
levelling, applied to every source on its way into the game.
- Loudness, the speakers option, the equaliser and levelling are now kept in
music-settings.json between sessions.
Setup page
- Remade in the mod's own colours and type, with numbered steps per source.
- Queue pinned to the right: now playing, the playlist's songs with covers,
find, and press to jump. Spotify's own queue shows once signed in.
- Cards for Local files and Online radio; equaliser and levelling under Sound.
In game
- MUSIC page in the menu: player and queue, sound settings, source status and
a button for the setup page.
- "Loading song..." notice in the corner for songs that take a moment, with a
switch to hide it. Presses made while a song is loading no longer restart it.
- The Spotify row is hidden when Spotify is not in use.
- Console: "spotify ..." is now "music ..." (music, music setup, music gain,
music through, music spotify client|login|logout).
Logs
- The music sources' working lines (traces, per-song and per-bind lines,
librespot's own) are debug only; stack captures are skipped unless debug
logging is on. Grind exits: the per-pop line is debug only too.
- A "Pace:" line when the game takes the sound faster or slower than real
time, to find why music sounds fast after tabbing back in.
Chat filter
- A run of digits is a number, not a word spelled with look-alikes: a race
time such as 2:55.0 is no longer masked.
LT / RT pressed during a grind pick the side the skater pops out to,
independently of the trick and the body's rotation (both come from the
left stick in skate.'s own controls). Only the sideways part of the
grind pop's launch is replaced, with the game's own sideways speed; the
trick, spin, pop height and along-rail speed are untouched.
- Input Mode: Toe / Heel (character relative, needs the stance) or
Left / Right of the direction of travel.
- Grab guard: a trigger used to pick the side is hidden from the game
until released, so it never starts a grab.
- Lean guard: while a claimed trigger is held in a grind, the left
stick's left/right reaches the game at the pop, so the game's lean
exit cannot drag the board into the rail.
- Off by default; switches in Mod Options > Player, the ReSkate skater
page, the tools menu and the console (grindexit ...). Kept between
sessions.
- Listener slots on existing hooks only: No Bail's physics step, the
trainer's set_trajectory detour, and the overlay's XInput capture.
No new detours. Build-locked to 20260929 through the trainer's byte
check.
Measured on build 25414733: 48 vanilla grind pops, 144 trigger-set
pops. See Extension/Skater/GRIND_EXITS.md.
Three outside sources can now be played through the game's own music
player. Each has its own row in the music screen, shown only when it is
set up, with tiles that behave like the game's playlists: track name,
cover, pause, next, and pressing a row to start that track.
How the sound gets in
- The game's song decoder is hooked. While one of these tiles is on, the
audio of the game song behind it (its "carrier") is replaced, block by
block, with the outside source's audio. The game's volume, fades and
pausing still apply.
- Rows are songs of our own registered with the game's music registry
(copies of a real song's asset with their own artist and title), so no
game song is borrowed or renamed to list a track.
Spotify
- The game is a Spotify Connect device ("ReSkate"), built on librespot
and linked into ReSkate.dll (SpotifyHelper/, Rust). Audio, track info
and controls come straight from Spotify. Needs Premium.
- Optional sign-in through the Web API lists the player's playlists and
Liked Songs as tiles with their tracks.
- librespot can no longer end the process (it does so on a free account
and on playback errors); only its thread is stopped.
YouTube Music
- Videos and playlists added by link. yt-dlp is fetched on request into
<Local AppData>\ReSkate\bin and checked against its published sums;
decoding is done in the DLL, so no ffmpeg.
- Private and age-restricted songs: cookies.txt or a browser's sign-in,
with QuickJS fetched as the JavaScript runtime yt-dlp then needs
(Node.js is used instead when installed).
External Audio
- Any app's sound, captured per process from Windows, with the app's own
output turned down while it plays through the game.
Setup page
- "spotify setup" opens http://127.0.0.1:8421/: device status, sign-ins,
YouTube links and tools, the external app, loudness. Loopback only;
changes are accepted from its own page only.
Also
- Speakers in the world play the tile's audio; grabbing one keeps the
player on their tile.
- Playlists added while the game runs are sent to the music screen live.
- MUSIC-SETUP.md: setup guide for players.
Building the Spotify device and the YouTube decoder needs cargo; without
it ReSkate.dll builds without them.
Map updates
- A map the game already has, being replaced by the host's version,
could load as soon as the new files were in Mods, while the mods
were still being applied: it ended on "This map is not loading". The
session now holds the load while a map is downloading, installing or
applying, as it waits for one that is missing.
- A player whose version of a map is later than the host's keeps it and
loads. Only an older one is replaced by the host's. Versions are
compared number by number (1.10.0 is later than 1.9.2).
Dedicated servers
- maps.latest_versions (off by default): the server asks Thunderstore
for the newest version of each of its custom maps, at start and every
maps.latest_check_minutes (5-1440, default 60), and writes it into
the mod's manifest.json. Nothing is downloaded: the server reads only
a map's level list and version, and players, who are held to the
server's version, fetch it as they join. The map being played waits
until nobody is on it. A folder that is not a Thunderstore package is
not touched, and the level list is left as it is.
- Console: maps update, maps latest on|off, maps latest every <minutes>.
Overlay
- Vote card: the pass mark is always in the middle of the bar. Green
grows over the left half up to the yes votes needed and over the
right half past them, whatever share the vote needs.
- The "Staff" tag is "ReSkate Admin", shimmering from #2500FF to
#B2F9FF.
No protocol change. Tests cover the version comparison and the
manifest write; the Thunderstore lookup was checked against the site.
The map update flow, the vote bar and the tag were not seen in game,
and the server setting has not run on a live server.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The entity and pending-update pages are twice the game's own size
(262,144 slots), down from four times. Every page set the game keeps
is that many times its normal size whether a map uses the room or not,
and the largest map load seen needs about 150,000 slots.
A page is a power of two entries, found by a shift, so the steps are
two, four and eight times; there is nothing between. The 434 patch
lines are the same instructions as before, each with the number for a
page of 0x800 entries. The render-instance pool and the spatial
buckets stay at eight times, which still covers the capacity.
The game can stream 20480 textures at once. When a map and its mods
need one more, it takes a slot past the end of its table and waits for
ever on a byte that is part of a counter: a hang, then a crash in the
hang's own report (crash reports of 2026-10-11).
The limit is raised to 32764, the most a texture's 16-bit handle can
index, before the game makes its texture streaming manager:
- The manager is allocated longer (6.0 MB, was 2.3 MB) and its slot
table and per-slot floats moved behind its end. Each of the 226
instructions that hold one of those offsets or sizes gets another
number; no code is added. Its four fixed lists are pointed at longer
storage once the constructor has run.
- The renderer keeps a block of floats per slot for each of four
layers. Its object grows to match (27 instructions), and the two
places that index a layer's block as layer * 20480 become a multiply
by the new count, in the same twelve bytes.
- Three functions keep 20480 entries on their own stack. Two list
filters go through a longer list a stretch at a time, and the sort
is given scratch room of its own past that many. Up to 20480 they
run as the game wrote them.
Every instruction is checked byte for byte first, and nothing is
changed if any differs or the manager already exists. An empty
texture_limit_stock.txt beside the game leaves the limit as it is.
Players are told when they come close: a notice at 90% of the limit
in force and an error at 100%, each once until use falls below 80%.
The log records each new high in steps of 5%.
Also: when a function of the game finds its stack written over, the
game ends itself with exit code 0xc0000409 and no crash report. The
log now names that function and its callers first.
The site table (texture_streaming_sites.inc) is generated from a sweep
of the supported build; texture_streaming.h describes the layout.
Tested in game below 20480 textures only. Nothing has run above the
old limit, where the three stack guards and the longer lists first
matter. At 32764 the game still hangs; the notice is the protection.
The launcher installs one mod folder per package, and the game takes one
mod's deck at a time, so a package could only bring one deck. A mod folder
may now hold a deck in each decks/<name>/, laid out like a mod folder. When
the mods are put together each of them becomes a mod of its own for the run,
listed as <folder>#<name>: it is chosen, set aside and switched like a deck in
a folder of its own. mods.json and the mod list only know the pack, which
counts as loaded when one of its decks is.
A pack's own layout is not one of its decks, whatever the folder is named,
and is set aside like any other deck model. It is what an older ReSkate
loads from the pack: 2.0.6 knows a deck by its folder's name only, so a pack
in a folder named Board_Break_<Name> with a breakable deck as its own layout
breaks boards there with that one deck.
Guard a crash when the game describes a failed load
When a load fails, the game writes "load <address> failed with error:"
and describes each part of the load's error in turn. That list can
hold parts that were never set, and the game calls through them: six
crash reports from one player on 2.0.5 and 2.0.6 all end at
Skate.exe+0x2e3ab9c, after eight parts that read "no error".
The guard hooks that one function. Before the game describes the
list, every part that is not one of the game's own objects is emptied,
so it reads "no error" as an empty part does; the description also
runs under an exception handler. The failed load is then the game's to
deal with, as it meant to.
What the game wrote goes to the log as a warning ("The game described
the error of a load that failed"), with how many parts were emptied,
so content that does not load can be traced. The game's message names
no asset, only an address.
A dedicated server that rotates its maps can now ask players where to
go next instead of taking the pool's next map.
Server
- maps.rotation_vote: when the timer runs out, a vote between three
maps drawn at random from the pool, "Random map" and "Stay here".
The most votes wins, a tie is drawn, and with no votes it is the
pool's next map. It ends early once everyone has voted.
- maps.rotation_vote_seconds (10-120, default 30) and
maps.rotation_extend_minutes (0-240, default 15; 0 leaves "Stay
here" out).
- maps.rotation_announce: a card when the map has 10, 5 and 1 minutes
left.
- The vote's maps are drawn a minute ahead and sent to games
(PacketKind::next_maps), so their pictures are ready when it opens.
- /vote extend: a built-in vote that gives the map in play
rotation_extend_minutes more. On by default (votes.extend_map), and
only runs while the maps are on a timer. "extend" is now a reserved
vote name.
- Console: rotation vote on|off, rotation announce on|off,
rotation stay <minutes>|off, rotation extend [minutes],
votes extend ..., vote extend.
Game
- The vote opens as a popup in the middle of the screen: a tile per
answer with its picture, name, share of the votes and count. It
holds input as the map download card does, and works with a
controller (D-pad, A, B), the keyboard and the mouse.
- Pictures: a mod map's icon from its folder, or from Thunderstore
when it is not installed; the game's own maps show their loading
screen picture, cut to its middle square. "Random map", "Stay here"
and a map without a picture are drawn as shapes.
- Older servers and polls are unchanged: the vote is a poll, so it can
still be answered with /1, /2... in chat.
Protocol 50: games and servers both need this build.
Votes: a vote is decided by the votes cast, not by everyone on the server. It
passes when yes is its percentage of them, at the end of its time, or sooner
once the players still to vote could not change it. A kick still needs two
yes. The card's bar is yes in green from the left and no in red from the
right, with a mark where yes has to reach. No protocol change.
Map versions: a map installed at another version than the host's is no longer
loaded as it is. The download card offers the host's version (MAP VERSION,
UPDATE), which replaces the installed one and is applied while the game runs.
For that the live merge keeps each mod's fingerprint with where its archives
were placed, and places a mod again when its files have changed since: before,
a mod replaced while the game ran was read where the old one lay. When the
host's version is gone from Thunderstore, or the old one's files are in use,
the player is told and leaves the session.
Dedicated server: "maps reload" reads the Mods folder again, so a map copied
in while the server runs can be switched to at once. The server window reads a
copy of the map list.
Map names: a custom map's level is dingolevel_reskate_<map>, and "reskate" was
left in its name ("/map reskate badlands"). The map's own name is shown and
typed now; the old spelling still finds it.
as a level unloads, the game takes each mesh variation out of its registry by the
entry's mesh and reads it unchecked, and this map's meshes unload before
their databases. A guard on that one function has the read of a mesh that is
gone go on with the next entry, and logs how many were passed over.
A 30 s profile on a 77-player server had four frames of 20 to 30 ms against a
usual 7.8 ms, from two things:
- The Park Editor read every mod's manifest every five seconds to list park
mods, on the game's thread and whether or not its list was showing (the
switch for that is never set): about 11 ms each time. The look every few
seconds now runs on a thread of its own and is taken up when it is done;
the one after making, renaming or removing a park is still at once.
- Connecting to a player asked Steam for their name, and waited on the Steam
client for 7 to 10 ms each time a player came into reach. The link takes
the name already read, if any: it only labels the link in diagnostics.
Profiler: a release ReSkate.dll has no symbols, and its functions were all
named after the nearest export. The check for that looked at the symbol's
size, but DbgHelp gives an export a size up to the next one. It now asks
whether the module's own symbols were loaded, and writes ReSkate.dll+0x<offset>
when they were not, to be named from the release's symbols afterwards
The chat filter matched short listed words anywhere inside a longer one, so
entries like "gger", "spik", "crip" and "coon" in the backend's lists blocked
"bigger", "spike", "script" and "raccoon".
- A listed word of four or five letters now matches inside a longer word only
when the rest of it is what goes around a swear word ("motherfuckers",
"bullshit", "shithead", "shitty", "xxfuckxx"). Words of six letters or more
still match anywhere.
- An ordinary word that equals a listed phrase without its spaces
("the rapist" / "therapist") no longer matches.
- bad_words.txt is gone: the filter has no words until the backend's lists
arrive.
Dedicated servers:
- "use_global_word_filter" (default true) turns the backend's lists off for a
server. Console: words global on|off.
- data/words_filtered.txt and data/words_blocked.txt hold a server's own
words, read at start and by "words reload". Blocked words follow
word_warnings. Filtered words are sent to players (new packet, kind 33),
whose games star them for players with the chat filter on and drop them on
leaving.
Multiplayer outfits: a player whose mods add or remove skater or board slots
no longer makes everyone look like the local player. A peer's items are put
in this game's slots by slot hash; slots the peer lacks stay empty and items
in slots this game lacks are left out.
A slam can break the local skater's board at the nose, in the middle or at
the tail. Half the time the grip tape holds the pieces together and the board
folds or an end hangs; otherwise a piece comes off, flies and lies where it
lands. The board is whole again in hand, on riding and on a respawn.
The pieces are four stretches of the deck in a content mod's copy of the
game's one deck model, each on a spare bone of the board's rig. The boards'
render pose hook only moves bones, because the replay records where a bone is
and not its size: a broken board's deck goes away and its stretches go to
where the pieces lie, and a whole board's stretches go away.
The deck the player chose loads first and other mods that bring a deck model
are set aside for the run, without touching mods.json. Another deck is chosen
while the game runs, through a live apply of the mods.
contrib/board-break.md says how it works and how a deck pack is made.
The Favorites order fix gave Favorites to any channel that asked for
playlist 0. That is what the music player asks for when Favorites is
chosen, but nothing said another channel could not ask for 0 and mean
something else. The channels the music player's own queues pick songs
for are now remembered as they are seen, and only those are given
Favorites for 0; on any other channel the request goes through as it
came. The channel numbers are still the game's: none is written down.
A channel's binding was logged once per change, except for a channel
past the end of the table, which was logged on every call. Those are no
longer logged at all. The binding line now says whether the channel is
one of the music player's.
The hook on the game's candidate check is removed, with its address: it
changed nothing and only logged.
Dedicated servers: roles, with their tags, commands and permissions
A dedicated server had one list of admins who could do everything, and
everyone else could do nothing. It now has roles, kept in
data/roles.json beside the bans: each a name, the tag its players wear
in the game, the server commands it may use, what it may do besides
commands, and a rank.
A server starts with five: player (everyone given no role: chat, votes,
parties, private messages), vip (a reserved slot), moderator (kick, ban,
messages, giving out lower roles), admin (everything) and muted. The
default role is whoever was given none; a role has what the default has
as well as its own unless it says it does not inherit. A reserved slot
is a permission, so a role's players have one without being listed.
Nobody kicks, bans or changes the role of a player whose rank is theirs
or higher, or gives out a role that is; the console is above every role.
"*" is every command but role and reserved, which a role has only when
it lists them.
New commands: role <player or SteamID64> <role>|none, roles, and roles
reload. "admin add|remove" and "admins" are gone: the admins an older
config lists are moved into the admin role the first time the server
starts, and leave the config.
In the game, a player's role tag and colour show in chat and on their
nametag, and each control in the Multiplayer menu, the map, time of
day, world layers and parks follows the command it needs rather than
one admin flag, as do the commands chat offers.
Protocol 48: the roster carries each player's tag, and a new
permissions packet tells a player what their role gives them. Older
games and servers do not connect to newer ones.
Also fixes a name clash that kept the server config tests from
compiling.
Replay drafts: the replay editor saves a draft as a pin on the recording
and a thumbnail in the game's media catalogue, which the Drafts screen
lists from. The game only makes that catalogue for a user signed in to
its backend, so under ReSkate a saved draft had nowhere to be listed.
ReSkate now registers the catalogue service and signs in a local user,
both with the game's own functions, so the game's Drafts screen works as
it was built: save, list, open and delete within a session.
A draft's footage stays in the recorder's memory, as it does in the
game, and does not outlive the session. Thumbnails of earlier sessions'
drafts are removed from the catalogue folder at startup; only folders
whose manifest is a replay draft's are touched.
Profiler: `perf sample` on the client thread now reports the slowest
client frames ahead of the function table, each with what it spent its
time in. An average over thirty seconds hides a single long frame.
Memory
- The entity and pending-update pages are four times the game's own size
(524,288 slots), down from eight times. At eight, the game kept a
3.5 GB block for them that was fully in RAM on custom maps; the largest
map load seen needs about 150,000 slots. The render-instance pool and
spatial buckets stay at eight times.
- The log has a "Memory:" line when the game's memory, the players in the
session or the skaters built change: committed, in RAM, players, skaters
built, player distance and map.
- Console: `perf memory` lists the game's memory by block size.
`perf memory mark` remembers it, and the next `perf memory` says what
was added since. `perf memory trace` also records who asked for each
block until `perf memory stop`. `perf memory big` lists the largest
blocks with how much of each is in RAM and a sample of what is in them.
RESKATE_MEMORY_TRACE=<megabytes|all> starts the trace with the game.
Replay crash guard
- The game's replay recorder crashes when its stream has no room for the
next chunk: it writes to the chunk it was not given (Skate.exe+0x48efa34,
crash reports of 2026-10-10). The function now runs under a handler
that, for that one fault alone, returns as if no chunk was ended, and
says so in the log. Any other fault still goes to the crash reporter.
The skip path has not been triggered in testing.
Launcher
- `-map <name>` (also --map, -map=<name>) loads that map as soon as the
game is up, named as the console's `load` names one. With --no-gui and
no other options the launch still uses the saved launcher settings.
Pause menu
- The Multiplayer and Mod Options pages step aside while the game's side
menu, or a screen opened from it such as Settings, is over them. They
drew over both before.
- A button still held when a page comes back is not a press on it: the
Back that closed the side menu no longer dismissed the page as well.
Multiplayer cosmetics
- A player whose mods add or remove cosmetic slots (stickers, say) no
longer has the whole outfit refused. Their items go to this game's slots
by slot: a slot they lack stays empty, an item in a slot this game
lacks is left out, and the log says how many of each.
Map downloader
- A host or dedicated server now tells joiners which Thunderstore package
its map comes from (the Mods folder name plus manifest.json).
- A player without the map gets a card: icon, name, author, version and
description, with DOWNLOAD and CANCEL. It works with mouse, keyboard and
controller.
- Only packages listed as Maps in the ReSkate community are offered, and
never deprecated, rejected or NSFW ones.
- On yes the package is downloaded, installed into Mods and applied as a
live mod, then the session loads the map. A small card on the right
shows the progress, so the player can keep skating.
- The player stays connected while it downloads: the host gives a player
who says they are fetching the map up to 45 minutes instead of the
usual load time.
- When the server changes map with the player in it, the card says so.
- Protocol version 47: map package on map_offer and world_state, and a
"fetching the map" flag on map_request and world_ready.
Joining
- The pause-menu server browser shows a CONNECTING card over the page
while a join is under way, and a COULDN'T CONNECT card with the reason
when it does not get in.
- The session and voice tabs no longer show before the player is in.
- Leaving without the map (declined or cancelled download) no longer
counts towards the failed-join limit, on servers and player hosts.
Input
- A card that asks something holds the game's input until the buttons
are released.
- Steam's overlay hooks XInput in front of ReSkate and answers the game
itself, so a controller reached the game behind menus. ReSkate now
hooks that hook as well.
Mod merging
- Mods are read on several threads, at launch and for live merges. A live
merge runs below the game's priority.
- The paid-cosmetics check reads the game's files and the mods on several
threads.
- A live merge reuses the paid-cosmetics verdicts it already has and
reports its progress and timings.
Launcher
- NSFW mods and their categories are not shown in the mod browser.
Player list. A player on the developer, staff or homie list sees each
player's Steam ID in the overlay's PLAYERS list for the session they are
in, after "Host" / "Guest" and the distance, on their own row too. A click
copies it. The lists are the backend's, the same ones the tags come from;
every client in a session already knew the IDs, so this changes who is
shown them and nothing else.
Custom scripts. Nothing in scripts/Custom is read or run any more, whatever
the launcher's "Loose files" setting says: nothing used them, and a mod was
using the folder to load a DLL into the game (package.loadlib from its
init.lua). The loader is kept behind one switch (custom_scripts_enabled in
custom_script_loader.cpp) for the day they come back.
For that day, the loader now also takes Lua's ways of running native code
away before the first custom script runs: package.loadlib, require through
the two C searchers and package.cpath, os.execute and io.popen. If that
does not hold, no custom script runs.
The overlay's Mods page no longer has the INSTALLED / SCRIPTS tabs; it is
the installed mods. Editing the game's own Lua and config through loose
files is unchanged.
The game shows on the player's Discord profile where they are skating.
It talks to the Discord app on the same PC over its local pipe, with no
SDK; without Discord running nothing happens.
- Solo: "On <map>", "Solo skating", and how long they have been in.
- In a session: "On <map>", "Server: <name>" or "Lobby: <name>", the
player count, and how long since they joined. A session with a
password shows "Private server" or "Private lobby" and no name.
Joining: a status carries the session's join code when anyone may walk
in (no password, not full, a dedicated server or a publicly listed
lobby). Discord shows that as "Ask to Join"; the game answers yes for
the player at once, and the other player's game is handed the code and
joins. If their game is not running, Discord starts the ReSkate
launcher, which the game registers for under the player's own
discord-<application id> URL scheme.
Switches: "Discord status" in the launcher's settings (off starts the
game with --no-discord, and the game never opens Discord's pipe), and
the same in the in-game Multiplayer settings, kept in the profile.
The model gains join_code: the code of the session a player is in as
host or guest (invite was a host's only).