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.
Apply and remove look at the player's own presets before the built-in
ones, and loading took saved names as they were. A preset saved under a
name a built-in preset took later (Hardcore now, any new one later) hid
it: the built-in's button and `trainer preset apply` applied the
player's preset instead, and the menu showed the name twice.
When the trainer loads, such a preset is renamed to "<name> (yours)",
numbered when that is taken; the applied list and the maps that apply
it follow, and the log says so. Saving and loading now share one rule
for which names are reserved (reserved_preset_name) and one length
limit (max_preset_name). The rule and the rename live in
trainer_presets.cpp, where the trainer_presets test covers them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoGm9ocG379TYfL2WC7yks
PRACTICE > TELEPORT now shows the waypoint the player placed on the
game's pause map, with a "Go to map waypoint" button. The console gets
`trainer waypoint [info]` and `trainer ground <x> <z>`.
Reading the waypoint (trainer_waypoint.cpp)
- It is a point of interest in the map's registry, the one ReSkate's
party markers register into (addr::native_party::map_manager), with
kind DingoMapPOIType_Waypoint (3) and its world matrix at @0.
- The registry is walked only while the TELEPORT card is drawn (the
page notes it, like the class list) or the command runs, every
250 ms at most. A player who never opens the card pays nothing.
- A registry the map has filled without a waypoint means it was
removed; an empty or unreadable one keeps the last waypoint seen. A
new level forgets it.
Landing on the surface (trainer_landing.h)
- The waypoint's height is not the ground: teleporting to it dropped
the skater through the map. local_ground_height() casts the park
editor's downward ray (editor::cast_surface) through the client
physics world and takes the topmost hit.
- The first ray starts at 1500 or 500 above the hint. A custom map
can sit higher than that (a waypoint at Y 12 over ground at Y 1939
was seen in game), so a miss is cast again from 20000.
- If collision there is still streaming in, the teleport goes to the
hint's height and a 12 s watch puts the skater on the surface when
it arrives (at most 6 corrections; the teleport is re-sent up to 3
times if it never arrives).
- Teleports still go through go_to(), so they follow the host's
noclip / teleport permission in a session.
The decisions (ray spans, the watch, remember/forget) have no game in
them and are tested by trainer_landing (DINGOSDK_BUILD_TRAINER_TESTS).
README and CHANGELOG updated.
Each built its text with path::string(), which throws for a character outside the ANSI code page, so the real error or message was replaced by a code-page error.
Follow-ups to the trainer (PR 37), and one multiplayer change.
Off until asked for (Extension/Trainer)
- The jump read-out and the LB+RB controller shortcuts start off. Jumps
are logged only while the read-out or telemetry recording is on, and
trick launches only when the trainer scaled them. A saved trainer.json
keeps what the player chose.
Memory search on demand (trainer.cpp, trainer_classes.cpp)
- The search for the game's tuning classes reads all writable memory.
It ran for every player after a level loaded, and up to seven times
when it found nothing. It now runs only when a class value or the flip
speed is not the game's own (the player's, or a host's they skate
with) or the EVERYTHING list is open, and at most three times a level.
- For those players it runs again on each level, which brings fresh
copies; a search from an earlier level used to count as done.
Guests under enforced physics
- A guest is locked by the session's enforce setting, from the moment
of joining (Engine/Game/Multiplayer/session_physics.h), not by whether
the enforcement status text was set yet.
- A guest's class values no longer come back after a map change.
The host's whole setup reaches its guests (protocol 39)
- Only Gameplay/SkatePhysicsTuning travelled, so a host could push,
sprint or jump further than the guests it held to the game's own.
- New packet physics_extras (30): the host's class values, trick
multipliers and auto push (trainer_session.h), sent when they change
and to players who join later, taken from the host only. A dedicated
server's are the game's own and it ignores the packet.
- Under enforcement a guest's own trick multipliers stand down for the
host's, whatever the boosts switch says. With enforcement off nothing
changes.
- A guest refuses bytes that are not an encoding, holds values to the
trainer's own ranges, and leaves out class values written for another
game build's table.
- protocol_version 38 -> 39: released versions disconnect from a peer
that sends a packet they cannot decode.
Out-of-view players are posed every frame again (Session/session.cpp)
- Players outside the camera's view and past 8 m were sampled at 20 Hz
(b763631). The game's replays record each skater as it stood every
frame and their camera looks anywhere, so those players moved at
20 Hz in replays. Removed; the distance rule (20 Hz past 200 m, 10 Hz
past 350 m) stays. This gives back the client-tick time it saved on
busy servers.
Presets, trick sliders and the values share one screen and one set of values, so a preset
cannot say "on" while a slider says otherwise. Every built-in preset is a dial (1 = the game's
own). Reset everything clears locks and trick sliders too. A player's grinds, pumping, climbing,
vaults and bails confirmed 56 more class values as read by the game (243 of 314).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018szL7EmApPZxh9GK4Phw6x
The game keeps much of its tuning in data-defined classes (push speeds,
everything on foot, dive and glide, bail speeds...). The trainer finds 24 of
them in memory by the defaults and field order the game's data ships and lists
their 314 values by name. Push speed now drives the push class's own speeds;
auto push, off-board jump height and wider trick height ranges come with it.
The Tune tab opens on a Realistic or a Fun short list. Built on ReSkate 1.0.7.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018szL7EmApPZxh9GK4Phw6x
The game hands its auto push flag to the animation and nothing comes of it.
With it on, the trainer's push code now brings a rolling skater that is not
braking up to the auto push speed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The game's trick scripts launch the no comply and the boneless through its
jump-trajectory setter; the trainer multiplies the launch speed there, told
apart by the physics states around the call. Pushing is scripted too, so the
push tuning values were never read: the top pushing speed is now applied after
each physics step, and push strength is hidden.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hippy jump's height is set by the game's trick scripts, not by the tuning asset (traced:
no tuning value is read for it). The trainer recognises one starting (riding -> off board,
rising) and scales the off-board upward velocity through the existing off-board velocity
path; a plain dismount is not rising and is left alone.
The jump read-out gains degrees of spin (and the peak rate) and of body tumble. The table of
tuning values the game reads now includes offsets seen in traced play sessions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JumpMaxHeight, JumpMinHeight and MaxSpinSpeed are not read by the game. Editing one now
scales the jump-height or body-spin graphs by the same ratio, so they work as plain
values; presets go through them. A preset reports the locked values it left alone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Players changed values such as the Mode ollie heights and saw nothing happen: no game code
reads them. trainer_used.inc lists the asset offsets the code was found to read (the asset
pointer, the per-group accessors and the skater's mode pointer were followed); rows without
evidence are hidden by default and marked when shown. The Essentials list and the built-in
presets now only use values with evidence.
Switching one preset off keeps the values of the presets that are still on.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
All new code is in Extension/Trainer. The game thread owns the trainer's state; the menu
page only queues `trainer ...` console commands and reads two snapshots.
- physics_tuning_model keeps the field and curve names the game's EBX carries, so the
editable table is built at run time from the player's own data.
- physics_tuning exports game_tuning / read_live / write_live / refresh_skater, with the
same field whitelist and curve rules as the session sync.
- no_bail publishes the physics state the local skater's selector last chose (air time,
wipeouts).
- A guest under host tuning cannot edit; teleports follow the host's noclip permission.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>