- reskate.entities.blueprints lists the game's object blueprints (meshes only if asked),
and reskate.entities.bounds gives a blueprint's box, so a mod can pick a prop by list or at random
- Attached objects take upright, turn and ground (a floor found within reach below), the "root"
joint, and a 6th options argument with solid; spawned props' bodies are found and ghosted
- reskate.skater.hide, reskate.world.pick, and skater.get gives right and switch
- reskate.console.command takes an optional 4th table of subcommands; the console lookup
suggests plugin commands word by word, and plugin commands lists the subcommands
- objbody: a test command for finding which body setter takes a prop out of collision
The SDK knew grind exits: reskate.skater.grind_exit took a toe/heel/left/right wish, and C++ worked
out the rail's direction, toe and heel from the stance and facing, the 1.8 m/s and the strength.
That logic is now the grind-exits mod's own Lua (plugin/exits.lua, kept on this machine). What has
to run on the physics thread is general:
- Extension/Skater/launches: every launch of the local skater (ollie, grind, no_comply, boneless),
classified by its call site and the state before it, with the board's direction of travel; the
`launch` event for plugins with events.skater; reskate.skater.shape_launch(from, {across, along,
up}) sets those speeds against the travel, one plugin a kind.
- reskate.skater.get() gains `right` (the root's right axis) and `switch` (the game's stance flag).
- Extension/Skater/pad_hide: reskate.input.hide's pad filter, out of the old grind exit file.
Removed: reskate.skater.grind_exit, the grind_exit event, GrindExitWish and GrindPop.
Physics state 701 is a grind too (the game checks it with 400 in about a dozen places; the
2026-10-11 measurement only reached the 400s): pops out of it were dropped, and skater_state called
it "other". Launches, skater_state ("grinding") and the trainer's grind pop all count it now.
Tests: the grind-exits plugin test drives the new path (11 checks of its own).
The game's skater loader builds each recipe slot with that slot's own function, which puts the
item's appearance preset on a material named for the slot's kind ("Outfit_Top_mat"). An item worn
on top sits in another kind's slot, so its mesh found nothing under its own material ("Bottom_mat")
and drew white. The preset lookup guard's addEsShaderPreset hook now also puts the preset of an item
on top on its own kind's material (layer_materials.h: which presets those are, from each item's
catalog folder and AssetPaths; the five native functions checked by their first bytes).
The outfit service logs what goes on top and where, the slot trials, why an item is not worn and
what it waits for.
Resolved where main's trainer meets the plugin branch:
- Board Break, the drone, Road Rash, Hall of Meat and Grind Exits are plugins here, so their
built-in code stays deleted: the trainer no longer hands Board Break's wear values back (the
plugin does not tune physics), its QUICK SETTINGS list no longer names Grind Exits or Board
Break, and the drone's debug action and menu are not brought back.
- The trainer's pump power, gravity, revert boost and transition help run beside the plugins'
pushes and velocities on each physics step; both sides' new declarations are kept.
- The game's own pad reads go through game_pad_read (the gamepad filter, Dark Pop's D-pad hiding,
the grind sticks) and then the plugin branch's menu and hidden-button handling.
- The trainer's forced grind settings are noted as the stance like any other value.
From 2.1.1's Sentry reports (60,000 in three days, 2026-10-21):
- Textures: a stale render texture handle takes the game's default texture instead of reading
through null (Skate.exe+0x3e897fb, a third of all reports, every one with TheVlixOne-Nike_Pack 2.3.x).
- Physics: the wake pass's bitset is made as long as the world's owner list once indices pass
20000, after long multiplayer sessions (+0x3d99e2f, +0x3d9ee43, +0x3d9ee47).
- Music: more than 1024 candidates are picked by ReSkate; the game's shuffle wrote past its stack table.
- Streaming: a staged read's piece that reaches past its 64 KB buffer is left unfilled (VCRUNTIME memcpy).
- Multiplayer: a remote skater or board whose parent bus link is damaged is left alive instead of
destroyed (+0x2b94cd0).
- Mesh pool: on an integrated GPU its budget is capped by the RAM and page file free; the Memory
line says what the PC has left.
Launcher:
- A folder with non-ASCII letters is a hard stop (the boot crash +0x18df4cf came after "Start anyway").
- The game's data files are checked against the supported build's sizes (from Steam's manifest);
missing, cut-short or online-only files send the player to verifying.
- One game per folder: the launcher will not merge or start while one runs there, and ReSkate.dll
holds the folder's mutex.
- Advice on the ready screen for PCs short of RAM and page file; reports carry pc.ram_gb and
pc.commit_free_gb.
Stuck loads:
- A load in one stage for two minutes sends a stuck-load report (every thread's stack and the log,
error.type stall, load.stage, load.seconds) while the game goes on (crash protocol 6).
- A load the player closes sends its timing with the stage it was in.
Logs: the PanelTrace diagnostics and the neighborhood unlock override's observation lines are gone.
- reskate.room.open, leave and state, with the room permission: ReSkate walks the game's menu to the Skaters page (its navigation events and RB on an internal scripted pad, as PR 297 and PR 230 did), draws the local skater on the room's floor and hides the room's own skater and board; other players are not sent the room. Extension/World/character_room, the room placement and hiding in skater_animation, and post_native_ui_event made general.
- reskate.input.text: what is typed while the plugin's screen is open; while it types, Esc is the plugin's.
- Animator Studio opens in its room (the Studio room setting, as the PR's), under its loading screen, or where the skater is when the walk fails; its card's name, trick and numbers are typed into in place.
- The studio plugin draws PR 297's screen itself: skate.'s Trick List menu and card, the joint timeline with key rows, dragging joints on the skater, keyboard and pad controls with their prompts, the unsaved-changes card, the recording strip and the loading screen. It lists a trick library made with tools/make_trick_library.py (the PR's, here beside it), and checks itself with studio test.
- Drawing: d:rough_rect, d:halftone, d:scribble, d:clip, d:measure, a text wrap width, the title_large font, text up to 400, and the loading screen's kickflipping board as d:game_image("loading_board") with a frame.
- reskate.ui.screen with panel = false: input taken, nothing drawn, the plugin draws it.
- reskate.skater.joint gives each joint's parent; reskate.skater.pose takes an offset.
Two general trick APIs on the hooks TheRealEJR found for his Rock to Fakie mod (pull request 285):
- tricks.label(from, to) ("hud"): the trick ticker shows `to` where the game's label is exactly
`from`, on the ticker's own call route and in its own buffer only, when it has room; 16 labels a
plugin, given back when it stops.
- tricks.record_as(from, to, within_ms) ("skater.control"): the next trick the recorder gets whose
id is one of `from`, within the window, is recorded as `to`, through a changed copy of the packet.
One ask a plugin, used once; refused while a throwdown runs, as trick replacements are.
The hooks pass everything on unchanged while no plugin asks.
contrib/plugins/rock-to-fakie is his feature in Lua: while the physics state holds a stall (400 or
401) the ticker shows Rock to Fakie for a Board Stall or a Lip Stall, and B pressed in it asks that
the stall be recorded as the game's fakie_rock (187). Off until `rocktofakie on`. Checked in the
plugin tests, with the new functions' permissions.
The new services and throwdown kit API files use add_functions and the shared
helpers; add_functions opens a read-only parent for reskate.throwdowns.flag.
throwdowns.release_flag stays removed, as plugins has it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every export was forced to 100 Mbps, about 750 MB per minute of replay, whichever quality
was chosen. The bitrate patch is removed; the frame pacing fix stays.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A lock tool like noclip and boosts. The player's game enforces it: the roster
carries guest_ollies (protocol 53), and the jump hook gives a locked ollie no
upward speed. Plugins get player.permissions.ollies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Hall of Meat (contrib/plugins/hall-of-meat) and grind exit triggers
(contrib/plugins/grind-exits) are Lua plugins: the bail model, its Meat card,
each map's best, the trigger rules, settings, console commands and menu panels
are theirs, with their own checks that dingosdk_plugin_tests runs.
What has to run in the game stays in core as plugin services:
- every physics step of the skater (skater_physics, the physics_step event,
permission events.physics);
- the skater's x-ray (xray, xray_render, xray_skeleton; reskate.skater.xray and
the draw handler's d:skeleton);
- skate.'s bottom left HUD corner (hud_corner; reskate.hud.corner);
- the game's slow motion, never in a session (game_speed; reskate.time.slow and
speed, permission game.speed);
- the side of the next pop out of a grind and pad controls kept from the game
(grind_exit; reskate.skater.grind_exit, the grind_exit event, reskate.input.hide);
- skate.'s own pictures by name (game_images; d:game_image), d:gradient and a
font for drawn text.
Removed: Extension/HallOfMeat, grind_exit_control and its commands, the HALL OF
MEAT and GRIND EXITS add-on pages, home tile, hub and native menu rows, their
menu models, and the hallofmeat and grindexit built-in commands.
The forward, up and off-board up boost speeds defaulted to +20, which counted as not
stock. They now default to 0: the boost adds nothing until a speed is set, and resets
(RESET EVERYTHING, the console resets, restore_debug) put them back to 0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Forward, up and off-board up boosts take a typed value up to 10000 (sliders still end
at 300 / 25), Revert Boost strength up to x100. Noclip, No Bail, Dark Pop, a boost
that is not +20, Revert Boost, grind exit triggers and Board Break are listed at the
top of QUICK SETTINGS with their values (Go to, Reset), make the SANDBOX line say
MODIFIED, and RESET EVERYTHING turns them back.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The trainer keeps upstream's page and naming (SANDBOX; noclip, No Bail and
the boosts on Skater > Cheats; the full screen controller menu) with its own
TUNE sections, presets card and search inside. The MOVEMENT tab is gone:
`trainer open movement` opens Skater > Cheats, where the Revert Boost rows
now sit. Upstream's Hardcore preset sits beside Realistic; saving
trainer.json and reading the map file stay off the client thread.
Board Break's wear and the trainer no longer undo each other: wear writes
four tuning values itself, and every trainer write used to put them back to
the trainer's copy (and a level loaded on a worn board took them as stock).
board_break::handling_value() tells the trainer what wear wants; values the
player has not changed are handed back to it, their stock is the game's own.
Tests: Hardcore's check follows the sections (topic, not the old lists);
the session test pokes a flip speed past the trainer's own limit (100).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Chat's flood control refuses a line identical to the one before it within 15 s, which also
caught /2 in a poll right after /2 in the last one. An answer to the running vote or poll is now
exempt from that one rule, on the server and in the game; the pace still holds, and the vote
still counts one answer per player.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shader expression registry registers a database from a queued job, and builds its per-key item
lists from the database's lookup without a bounds check. A database destroyed before its job ran,
or a lookup index past its items, left the registry holding pointers that crash it later
(Skate.exe+0x3f7cf33, +0x3f7ceed, +0x20960b in Sentry). The guard skips a registration for a
database already destroyed and points an out-of-range index at a valid item of the same key,
logging each.
A rebuild builds one segment per key and each needed every key's slot, so a long replay built
them once per segment: the game's rebuild is hooked too and the slots built for its first segment
are kept for the rest of it (per Behaviour and Subject). The segment player is no longer hooked
(it only counted plays for keysmooth status, every frame). The smoothness is a slider beside the
switch in the overlay (sent when let go, as each change rebuilds the open replay's camera) and
Keyframe smoothness (100, 75, 50, 25%) in the pause menu and Mod Options > Visuals. The startup
line logs on the runtime channel.
The game plays the replay camera's framing keys as cubic Hermite segments whose speed changes at
every key (the acceleration jumps there, and it brakes where a channel slows or turns back), so a
free-camera shot surges and settles around each key. keysmooth on rewrites each segment as the game
builds it, so that the game's own player plays the natural cubic spline through every key: speed
and acceleration carry on through the keys, and every key is still met exactly, at its time.
- keysmooth on|off (off by default), keysmooth amount 0..1 (1 by default; 0 is the game's curve)
and keysmooth status; kept in %LOCALAPPDATA%\ReSkate\KeySmooth.settings. Only the free camera
(Behaviour 1) is smoothed; the other camera modes keep the game's curve.
- A switch in Mod Options > Visuals and in the overlay's Skater > Freecam.
- The curve apart from the game, with tests (DINGOSDK_BUILD_REPLAY_KEYFRAMES_TESTS); the hooked
functions are fingerprinted, and a build that differs gets no hook.
- contrib/keyframe-smoothing.md: what it does, how, and what was seen in game.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scrolling shirts onto Yudi-Yuddispack's philippleinshirt_ap crashed the game (Skate.exe+0x3dc07ed):
one of the preset's set parameters has a type of 0x0c07010304040502 (null in another crash). The
game type-checks every set parameter when it next applies a preset to a material, reading the
type unchecked, and catching that crash inside addEsShaderPreset was too late: the preset was on
the material already and the next update crashed on it.
addEsShaderPreset's guard now checks the preset first, as that type check reads it: the TypeInfo,
its data, a word at +4. A preset with a parameter whose type cannot be read is left out as a
preset that is not loaded is (the item shows without it), and named in the log once with that
parameter's key. Many of the game's own types are built at run time, so the test is that the
type reads, not that it lies in the game's image.
The ReSkate menu now has two styles, switched with View / F11 or the buttons in its top bar
and saved with the profile (`ui fullscreen`):
- Window: the mouse menu as before, with a side rail of pages in groups.
- Full screen: made for a controller, in the pause menu's look. Pages across the top (LB / RB),
each page's sections as the ONLINE page's tabs (LT / RT), button prompts at the bottom, the
pause menu's sounds, and the pause menu's own Multiplayer pages as ONLINE.
The pages are regrouped: HOME (quick switches and a tile for each page), MAP, BUILD, SKATER
(freecam, first person, cheats), SANDBOX (once Trainer), ADDONS (drone, Board Break, Road
Rash, Hall of Meat, grind exits, music), ONLINE, PROGRESS, MODS, SETTINGS, DEVELOPER.
Full screen, every shared control is drawn for a pad: switches and option rows changed with
left / right one step a press, sliders as steppers that repeat when held, rows of buttons,
and pages that scroll with the right stick. Search boxes and typed values stay in the window.
A pad press takes the menu's focus back and hides the pointer until the mouse moves.
Also: map pictures in the map picker (official loading screens, a mod's icon) with a search
box in the window, Splash and Tutorial Island left out of it; number boxes beside the
window's sliders; the binds in groups, with an action bind to open the menu (`menu`); a menu
size for each style (`ui scalefull`); the time of day stepping from the one last asked for.
A model is spawned at each other player's flying drone and moved every frame. It is the drone's
own model (FPV Drone by PinaPipe, CC BY 4.0, credited on the DRONE page), built by ReSkate Studio
into a pack that ReSkate.dll carries and unpacks into Mods/.reskate-builtin before the merge.
Without the pack the game's traffic cone stands in. dronemodeltest spawns one in front of you.
Most crashes are caught by the game's own fatal handlers, which write a
dump of thread stacks only; the launcher could only copy it after the
game had exited. A crash on a freed or overwritten object then showed
the faulting registers but not the object or the list it came from.
- game_fatal_dump: the game's MiniDumpWriteDump import points at
ReSkate. Before the game writes its dump, the crash reporter takes its
own of the still-running game for the faulting thread; the game's dump
follows as before. Reports say capture.source "game-fatal-handler".
- The reporter's dumps include the memory the stacks point at
(MiniDumpWithIndirectlyReferencedMemory), a few MB more a report.
- The shared report says who asked for the dump (protocol version 4).
- Tests: a game-handler crash is captured with a heap block only its
stack points at; ReSkate's own capture keeps it too.
A spatial transaction queue gives every object it tracks a cull ID from a
free list made once, when the queue is, at the game's
InitialCullIdListSize (40,000 in its config), and never grown. A custom
map with more objects (Cardiff) emptied it while loading and the next
insert read entry 0xffffffff of the list (crash at 0x489cfe9).
- spatial_cull_ids: as each queue makes its list, its size setting is
raised to 262,144, one ID for every entity slot the entity pages hold;
a larger configured value is kept. 12 bytes an ID per queue.
- The contract checks the list maker and how it reads the setting; it is
installed at startup with the other world pools.
719cba1 stopped writing the metadata pointers into the other renderer
(0x2daa00 bytes, Skate.exe+0x30c64cc, kept at 0x75f2700), taking them for
the state owner's alone. They are not: the other renderer's own code reads
them through the patched view (0x030e6bff, called from 0x30a7e18) and
append (0x03e94fe6, called from 0x30d8ebb). Without the write, it read
whatever lay 18 MB past its end. With it, as in every release before, that
memory was overwritten, or the write faulted when nothing was mapped there
(the store_field crash).
- The other renderer's allocation is patched to the state owner's
0x11ab080 bytes (0x030c64cc, mov ecx), so +0x11aae28/+0x11aae30 are
inside it.
- Both renderers' metadata pointers are written again, before and after
the native constructor. The resource-state and relation-mask vectors stay
the state owner's alone: the other renderer keeps fields of its own at
+0x2da920.
- The contract's comment says which renderer reads what, and where.
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.
Merges ReSkate v2.0.9.
- Wallies (the game's own switch) with their measured numbers; double / triple jump, jump
on release and wipeout bounce on foot; foot flip / spin speeds; no speed limit on foot.
- Flip speed and the per-trick Late flip speed now change late flips; Late flip start /
buffer rows.
- The trainer never changes the game's own settings (GAME SETTINGS rows only show them).
- Dark Pop 3.1 (Sivaes/darkpop, GPL-3.0), saved on and off like No Bail; Revert Boost 2.0.
- The search for the game's values walks memory once and shows in the panel.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Switching time of day could load the new layer while the old one was
still loaded: the apply loop goes through the layers in catalog order
and sends one event a pass, so going to an earlier time (WeatherDay ->
Evening) sent the load first, and nothing waited for an unload to finish.
Two times of day at once carry both times' lights, and a level with many
lights overflowed the engine's light tables.
A time-of-day layer is now asked to load only once every sibling time of
day that is to be off reports requested and loaded both false; until then
its row says "Waiting for the old time of day".
An unload is no longer sent while the engine is still loading the layer
itself. The engine ignored it, so at a level load the level's own time of
day could stay loaded beside the saved one, and with the wait above the
saved one never loaded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
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>
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.
Sets: save a selection under a name and place it again anywhere, on any map.
- The library gets an OBJECTS / SETS switch. SETS saves the selected
objects as a set and lists the saved sets to place or delete (Delete asks
for a second press).
- A set is a park file in <profile>/object_sets/<name>.park.json, so it
shares the park format, its 2 MB limit and its name rules. Positions are
relative to the set's anchor: the middle of its footprint, at its lowest
origin.
- Placing follows the cursor as one wire cube per object, on surfaces or
the grid plane; the mouse wheel turns it by the angle step. A click
pastes the whole set: one undo step, under the paste path's object and
session limits. Sets with objects missing from the catalog cannot be
placed.
- A placed or pasted group is then selected with the move gizmo.
Editing groups:
- Local axes work for multi-selections. A placed set's axes are the
rotation it was placed with, plus every later turn; any other group uses
its last selected object's rotation. Applying a moved group does not snap
its own rotation to the angle grid.
- Duplicate (button or Ctrl+D) copies every selected object at its saved
position, rotation and scale, one grid step along X, as one paste.
- On a server with object scaling off, pastes (sets, duplicates, Ctrl+V)
are made at 1x.
Tests cover set storage (anchor, round trip, replace, names, damaged files,
delete), saving and placing a set through the UI (surface, wheel, one
paste, no live preview), selecting it with its own axes, moving it along
them, and duplicating with and without scaling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes in the stock park editor that a multi-object edit (a paste, an undo,
a loaded park mod) runs into:
- Pasted objects lost their world link. A paste creates its objects one by
one and saves them when the whole edit completes, but the placements
runtime reconciles after every create and unlinked rows whose id was not
saved yet. Every create unlinked the ones before it: only the last pasted
object could be selected, moved or deleted, and deleting the others
removed them from the layout but left them standing in the world. The
reconcile now counts the editor's pending layout as saved.
- Scale was lost on paste, undo and park-mod load. The native create recipe
has no scale lane, so a created object spawns at 1x. Only the placement
preview resized it afterwards; a create step with a scale now becomes a
move, verified by the server tick's readback like any move.
- Scale was lost on level reload. Restored objects spawn at 1x and the
pose poll then saved 1x over the saved scale. Restores with a scale are
resized on the next server tick, and the poll leaves them alone until the
resize is read back.
- Delete did nothing with several objects selected. Delete (key or button)
now removes the whole selection as one edit and one undo step.
Tests: the transaction fixture's create now spawns at 1x like the game and
reconciles after every create like placement_finish_batch; new checks
cover pasting a scaled group, every pasted object keeping its link, moving
and rotating a pasted group, and multi-delete with undo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.