Plugins register modes (card, replaces, order, flag, players) and publish documents through
reskate.throwdowns.ui: the setup page, the waiting card, HUD slots themed on the game's HUD_<theme>_*
widgets, the match panel, flags and match restrictions. SmokeyDawg58's native plumbing
(throwdown_menu.cpp and its .inl files) renders them and posts throwdown_ui events back. kit::flag
is the one native flag registration. General services added for it: reskate.files, reskate.ui.dialog,
reskate.skater push/hold/bone_cam, reskate.camera.pad_flight, reskate.physics competition,
reskate.session.world, reskate.tricks feed/gesture, and world entities.
His ten modes are now Lua in his plugin: the rules, referee and wire (td/rules, referee, wire), and
every page and HUD document (td/ui*.lua). Extension/Modes and reskate.modes are gone, along with
every race and 1-UP branch in the SDK. The plugin test drives each mode through kit events.
A native setup page is never written to while it closes into flag placement: its rows are not
republished, the stock page is put back once the exit is over, and the stock Players slider is left
alone while a plugin flag is out. This fixes the crashes on the Deathrace button and on 1-UP's
Confirm.
The three features are Lua plugins in contrib/plugins (fpv-drone, board-break,
road-rash), built on what this adds to the plugin API: flight input, gamepad and
joystick reads with input capture, a rolled camera, a package's own sounds and a
synthesised motor (plugin_sound, plugin_wave, motor_synth), the skater's physics
events (skater_events), items worn over the outfit (outfit_overlay, skater_items),
the board's bones (board_pose), skater animation (anim_core), plugin images,
throwdowns started for chosen players, and server copies of plugins on a lobby
host and the dedicated server (server_plugins).
Removed from core: Extension/RoadRash, Extension/BoardBreak, the drone's flight,
radio, follow, OSD, beacon, outfit and sound code, its prop models (native_prop),
the DRONE, BOARD BREAK and ROAD RASH menu pages, home tiles and native menu rows,
their console commands, the drone follow debug action, the drone marker packet
(kind 36 stays unused; the roster keeps its byte) and the server's drones setting.
Kept in core, since every board needs it whichever plugins run: the breakable
deck packs (breakable_deck: which deck, its label and wheel spacing, the
boarddeck command and the BREAKABLE DECK card on the Mods page), and board_pose
keeping a deck pack's stretches out of sight, the baseplates on the deck and the
wheels at the deck's spacing on every board.
Players reported the game hanging for about a second while skating. A
profile showed the memory line in the log walking every heap
(HeapSummary) on the client thread, 0.5 to 2 s every two minutes. An
audit of everything that runs on the game's update and render threads
found more of the same, and Sentry's top crashes had causes of ours.
Freezes:
- Memory log line: no heap walk or thread snapshot; the GPU part and the
write happen on a thread of their own.
- Profile database: synchronous=NORMAL in WAL mode. A save is still kept
when the game crashes; every play event no longer waits for the disk
inside the game's hooks.
- ReSkate's own preferences and values (menu clicks, session sliders,
Discord) are saved by a background writer in order; reads see a value
until it is saved.
- Input: the foreground window and the keyboard are sampled on a thread
of their own (GetAncestor and GetAsyncKeyState stalled the client tick
40-50 ms when another program held Windows' lock); flight input uses
the sample; ClipCursor at most twice a second while a menu has the
pointer.
- Hooks installed while the game runs go in one transaction per place
(HookBatch): throwdowns, remote players, the party, Hall of Meat.
- Off the update thread: the host's map package lookup (one table read in
the background, not the Mods folder per offer), the trainer's map file
and settings save, Board Break's settings save, mod music playlists and
their covers, the Spotify station covers.
- Off the render thread: the Modding page's and the deck card's Mods
folder reads. The overlay's three startup waits share five seconds.
- Less work per frame: Steam names reused while one a frame is asked
again, 2 friends a tick, steam_api64.dll checked once per process,
cosmetic names read in place rather than a system call per character,
an outfit load no longer copies the inventory per slot, the throwdown
type scan runs at background priority over resident memory, the Board
Break deck label is kept a minute.
Crashes:
- Render-resource pool: the metadata fields are written to the state
owner only. The other renderer is 0x2daa00 bytes, and +0x11aae28 is far
past its end (store_field crash; heap corruption in every release).
- Other players' skateboard effects: a stale skateboard component is no
longer followed, and the joint lookup is walked with a bound first (an
endless loop in reused memory ended in GameStall).
- Launcher: Oodle decodes into the buffer it asks for with corrupt data
possible, and its assertions no longer stop the process (heap corruption
in the store-copy scan; a breakpoint in oo2core).
Timings (Send performance timings):
- A frame of half a second or more in a level is sent as game.freeze:
where the time went (ReSkate's tick, the game's frame, or outside it),
whether the renderer kept presenting, and ReSkate's busiest zones.
- Closing the game sends the level session it was in.
Music after a connection drop:
- Spotify device: an account Spotify could not be reached with is kept and
tried again (3 s doubling to a minute); a session that drops is taken
up again. Only an account Spotify refuses is asked for anew.
- YouTube playlist pictures that could not be fetched are tried again,
from 30 s to 10 minutes apart.
An FPV drone flown from the free camera: real quad physics (thrust,
gravity, drag, Betaflight "Actual" rates), collisions with the world, and
a goggle-style display. Open it with `dronemenu` (or the drone window's
Start flying).
Flying
- Keyboard and mouse, gamepads, or USB RC radios (EdgeTX etc. in Joystick
mode). "Set up controller" learns each stick and switch by asking for
one at a time, reversed axes and springing gamepad throttles included.
While a controller flies the drone, the game is kept off it, as
freecam's controller mode does.
- Presets (Tinywhoop, Toothpick, 5 inch), arm/disarm, back to takeoff,
and every setting live through `dronetune`, saved in the profile.
- Optional range: the picture breaks up and is lost with distance from
the takeoff point; past it, failsafe cuts the motors.
- OSD, on-screen sticks, an analogue video look, synthesised motor sound.
- Goggles: the pilot's skater wears eyewear from the game's catalog while
flying, and gets their own back on landing (Road Rash aware).
- Start flying hides the game UI, Stop flying brings it back.
Multiplayer
- New packet `drone` (36): where a player's drone is and which way it
faces, 15 times a second, unreliable, relayed like effects. Markers
glide between updates. Protocol version 52.
- FLYING DRONE above the name of a player who is flying.
- Players choose whether to share their drone and whether to see others'.
- Dedicated servers: `sync_drones` (players section) and `drones on|off`.
Off: no drone packets are relayed, and players are told in the roster.
Changes to existing code
- `flyspeed` takes any speed from 0.6 to 1500, not only the seven listed
(the drone's presets and top speed slider use speeds of their own).
- Overlay: begin/end_input_access, so the drone's controller read sees
the device while the game is kept off it.
- Profile: local_surface_hit, the park editor's ray from one point to
another, for the drone's collisions.
Tests: Skater/Test/drone_radio_tests (controller setup), and the drone
packet and roster rule in Multiplayer/Test/protocol_tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MArBL6UbYZPsp9rMnq2Ks5
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.
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
Host bans: the backend's new banned_hosts list (players the team has
stopped from hosting lobbies) is read with the other identity lists.
A listed player's own game refuses to host and stops a lobby already
running. Since that is theirs to change, everyone else's game does the
rest: the lobby browser leaves their lobbies out, and the game will not
join one by code or stay in one. They can still play on servers and in
other players' lobbies. The browser now also leaves out lobbies hosted
by globally banned players.
Server log: "Direct connection asked for by ..." is written once a
minute per player, with a count of the repeats. A player waiting for a
place on a full server asks again about every second, and each ask was
a line.
Other players' skaters and skateboards showed none of the game's skater
effects: not the trail or fire a costume or deck comes with, and not the
sparks, dust and puffs of a skater touching the world.
Costume and skateboard effects. The game gives another player's skater and
skateboard the right effect sets by itself, but builds them before their
outfit is on, so nothing showed. They are now built again half a second after
the outfit is applied. An always-on effect that a skater's item puts on the
skateboard's wheel joints (the Ghost Skater trail) follows the skater's own
copy of those joints, which only this player's skater keeps up to date: for
other players it is taken out of the skater's sets and built on their
skateboard, which has the same joints.
Contact effects. They come from a skater touching the world, which only
happens in that player's own game. Each game now captures its skater's
contacts (position, velocity, surface normal, surface material) and sends up
to three a tick in a new unreliable packet, PacketKind::effects. Other games
play each through the game's own function, so the effect is still chosen from
the surface and the speed. A contact is shown as late as that player's poses
are, only within 30 m of their skater, and only for a material the level's
table has: the game reads that table without a bound.
Server. A dedicated server relays effects packets to players within 150 m of
the sender, at most one packet a tick from each player. New setting
players.sync_effects (default true), console and admin command
"effects on|off". Off: the server relays none, and the roster tells games
neither to send their contacts nor to build other players' costume and
skateboard effects. Player-hosted lobbies relay through the host and always
share them.
The roster carries one more byte for the setting. Still protocol 45, which
has not been released.
New console command "mp effects": what has been captured and played, and why
the last contact was refused if one was.
Poses and skater sound between a dedicated server and its players use new
formats. Lobbies and the links between games are unchanged.
Poses (pose_codec.h, pose_batch.h):
- A pose is rounded once (rotations to 0.08 degrees, positions to a
millimetre) and sent as its differences from a pose of the same player
the receiver has acknowledged. Several players' poses share one message.
- The receiver acknowledges messages once a tick; the sender builds only on
acknowledged poses, or on the whole pose it sent reliably. Loss costs the
poses in that packet and nothing more.
- Fingers are not sent to players further than "finger_distance" (25 m).
- Games upload their own poses the same way, and the server acknowledges.
- About 300 B a pose at full rate against about 660 B before.
- The server no longer rounds rotations by distance: it saved little.
Sound (sound_codec.h):
- Each sample is sent as what changed since the one before, numbers rounded
to 1/1024. One reliable message a tick carries every skater a player can
hear. A stream is sent whole at its start and every two seconds.
- About 106 B a message against 368 B before, on a recording of real play.
Direct connections:
- "connection": "relay" (default) or "direct", with "direct_address" and
"direct_port". A direct server listens on UDP and is listed with its
address; players still sign in through Steam.
- Games connect straight to servers that offer it and fall back to the
relays after 5 s. Setting and "mp direct-connections" to turn it off.
Server:
- "bone_scale_limit" is 1 by default: resized body parts are off unless a
server allows them.
- Logs Steam's relay network status at start-up and in "net", and watches
the Steam sign-in.
- "pack_ms" (10): how long Steam may hold a message to pack it with others.
- "steam_debug": logs what Steam's networking says, for connection faults.
- "relay_everything": sends poses and sound through the server even between
games that have linked, for testing with few players.
- "net" shows pose sizes by rate, the share sent without fingers, and whole
poses sent.
Game:
- Only ReSkate's nametags are shown; the game's own are removed.
- "mp pose-dump <seconds>" records the local skater's poses and sound.
Tests: pose_codec_tests covers exact round trips, chains of differences,
held fingers, damaged input, the sender and receiver over a link that
delays, loses and reorders, and the sound format; with a recording as its
argument it prints sizes on real play.
Protocol 44: servers and games must both update.
- The crowd limit always sends a player the nearest 8 others at the
full rate and the next 12 at ten a second, whatever the budget. It
gave everyone the low rate first and the nearest what was left, and
with enough players on (about 120 at the default budget, fewer at a
lower one) nothing was left: nobody was at the full rate, and since a
skater's sound follows the full rate, nobody was heard either
- Whole pose and sound states are sent every minute instead of every 8
seconds. A whole pose measured 3.7 KB against 0.7 KB for a difference
from one, however old the reference, and at every 8 seconds they were
about a seventh of what a busy server sent
- "net" lists which of Steam's relay locations the connections go
through, and "net <player>" that player's: Steam reports pings of
several hundred ms on a server measured at 55-84 ms directly, and
this says which way the route went
No protocol change: a server built from this still takes 1.1.4 players.
Server: what each player is sent
- A crowd limit: each player is sent at most crowd_budget poses a second
(default 600, 0 for none; "crowd <n>|off"). Everyone at 5 a second at
least, the nearest at the full rate, the next nearest at 10. Before,
everyone within full-rate distance was sent at the full rate, which a
crowd of 40 turned into more than a connection carries
- A skater's sound goes only to players within the full-rate distance,
and in a crowd only from the nearest; it went to everyone
- A player who joins, or arrives after a map change, is shown the others
about seven a second, nearest first, and none while what they were
already sent is still on its way. Before, every outfit and every
stream went at once (and to everyone at once after a map change)
- Whole pose and sound states every 8 seconds instead of 2, on the
source's own clock, and slower pose rates sent on that clock too, so
recipients share one patch and compression (DeltaCache) instead of one
each. The settings survive a map change
- Object updates wait for a connection that is behind
Server: Steam connections
- send_rate (default 900 KB/s, "rate <KB/s>") is set as both Steam's
minimum and maximum, for open connections and each new one. With only
the maximum raised a connection stayed at Steam's 256 KB/s; set high,
what was lost was resent until the connection was full of resends
(about 1.1 MB/s reaches a player through the relays)
- A 4 MB send buffer, and a refused must-arrive message no longer ends
the player's session at once: 15 seconds of refusals do (30 for object
updates). A full queue used to drop everyone who was behind together
Server: seeing what is going on
- "net [player]": every connection's ping, delivery, queue and rate, the
traffic by kind (poses, sound, voice, outfits, objects), who uploads
most, and how busy the server's loop is
- A [network] line in the log every minute
- A leave or failed join says how it ended: closed by the player's game
with Steam's reason, connection lost, or closed by the server and why
- The queue time no longer prints Steam's "unknown" as a number
Server: other
- Reserved slots: reserved_slots and reserved in ReSkateServer.json, and
the "reserved" command; admins always have one
- The speed check flags a game at about 1.5x and over (was 1.06x), and
tells only that player; for the admins it is in the log
Game
- A remote skater with no pose for a second stays where it was for up to
eight seconds instead of being destroyed and rebuilt three seconds
later. On a busy server one slow frame did that to many players at
once, and the rebuilding made the next frames slower still
- Every waiting message is read each frame (at most 128 were)
- On an official server the role tags (Dev, Staff, Homie, Creator) show
for every player, not only the few the game is connected to directly;
community servers keep the old rule
- The server browser searches Steam at most every 15 seconds (45 after an
empty answer) and keeps the last list for 15 minutes when Steam returns
nothing, up from 2
- The game's own connections send at up to 900 KB/s
Server browser
- Official servers (the backend's "official_servers") sort first and are
drawn in ReSkate blue
- Servers Steam friends are skating in sort next, drawn in green with the
friends' names; searching a friend's name finds their server
- A player in a session they joined can join another server straight from
the list; hosts still end their session first, and a Steam friend's join
offer never replaces a session
- Joining a dedicated server from the list records it as the player's
public session, so friends see it and the row shows HERE
- Blocked servers (the backend's "blocked_servers") are hidden and cannot
be joined, by code either
- When the backend sets "server_tokens_required", dedicated servers that
signed in without a login token are hidden, except on the player's own
network
- A search that gets no internet list from Steam keeps the last list for
two minutes, and waits six seconds before treating it as empty
Roles
- New Staff tag from the backend's "staff" list: green name, badge and
item animation, ranked after Dev
- Staff can pick a rainbow for their items on the Special page; it is sent
as a solid colour older builds can show, so the protocol is unchanged
Dedicated server
- steam_token: sign in with a Steam game server login token and keep the
same Steam ID every start; empty signs in anonymously as before
- A listed server without a token says so at startup, and when the token
rule starts or stops hiding it
- Linux servers update themselves from the release's "server_linux"
tarball, with the machine's curl and tar, pinned by SHA-256, and carry
on in the same process
Launcher
- Only create the MiniDumpWriteDump API-set shim under Wine/Proton
- Mod merge: rebuild a merged bundle's chunk metadata the way the game
writes it (one record per chunk, in chunk GUID order), so costumes
from different mods built on the same game costume keep their
textures and meshes
- Merge audit checks that layout, and there is a new unit test for it
- Server names: only letters, numbers, spaces and - _ [ ] ( ),
1 to 64 characters; the server browser hides servers with other names
- New Centrix tag from the backend's "centrix" list: blue and white
name, badge and item animation, ranked after Dev and before Creator
Cap outfit packets and stop dropping recipients when a relay cannot be
built. Add per-player relay limits for outfits, skater sound and voice,
and a back-off for Steam IDs that keep failing to join.
Throwdowns: only participants can write rows, turns and scores; a copy
that fails to spawn no longer blocks the others.
Throttle remote skater and skateboard re-creation, cap remote objects
per session, and show developer and friend marks only for identities
Steam vouches for. Route all connections through Steam's relays.
Dedicated server: validate map changes, clean player names, keep vote
cooldowns across reconnects, and cut ban labels on a character boundary.