- The focus starts on PLAY when the launcher opens with a pad connected, and
on the open tab (GAME, MY MODS) when the pad opens Settings or the Mod
manager.
- List rows act as one item: up and down go from row to row in one press,
and a row's buttons take the focus only from the row (D-pad right).
- D-pad left and right between the rail and a list: ImGui looks to the side
only along a thin band through the focus and missed items level with the
gap between two rows, so a side move that finds nothing looks again across
the focus's full height.
- The left stick scrolls the panel under the pointer directly instead of
through wheel events, which queued behind the pointer's moves.
- On a Steam Deck (SteamDeck=1) the right stick gets a trackpad's feel: small
swipes move the pointer, and the glide after the thumb lifts is cut.
- The MODS tile shows Thunderstore's mark in its own green instead of
the skate tool, which stays as the fallback when the mark is not
built in
- "Open Thunderstore" in the mod manager is a branded button: the mark
and name in Thunderstore green on its dark blue, taller than the
plain buttons beside it
- The mark is a new asset, assets/launcher/icon_thunderstore.png,
embedded like the other tile icons
- A disabled mod's update is no longer asked about before PLAY or
counted on the MODS tile, and "UPDATE AND PLAY" no longer installs
it. A disabled mod does not load, so its update stays on the Mods
page, where it can still be updated by choice
begin_panel and begin_page focused their window again every frame. With a
controller that pulled the focus out of the page's child panels, so in
Settings and the Mod manager (GET MODS included) the D-pad and left stick
could reach nothing. The window is now focused only when the focus is not
already inside it.
While the pad moves the focus the mouse cursor hides, and the next mouse
move brings it back.
- Settings' and Mods' rails, pages and lists are NavFlattened children, so
the D-pad crosses between them instead of treating each as one item that
only B could leave.
- The pad's pointer no longer moves the system cursor. Under Proton that
move came back late as a mouse move and dropped the pointer; now a mouse
move counts only when it lands somewhere new.
- The first R3 only shows the pointer, so a stray click does not press
whatever the mouse last rested on.
The launcher read no controller at all. Started from Steam (Desktop or Game
Mode on a Steam Deck), Steam Input applies the game's gamepad layout to it,
where the right trackpad is the right stick and its click is R3, so the
trackpad did nothing; only the touch screen, which arrives as mouse
messages, worked.
- Every XInput pad is merged (Xbox, Steam Input's virtual pad), else a
DualShock 4 / DualSense is read over HID with the reader the in-game menu
already uses, now its own library (dingosdk_playstation_input).
- D-pad and left stick move ImGui's focus, outlined in the launcher's blue;
A / Cross presses. The hand-drawn tiles, switches and menu buttons take
part in navigation (ImGuiButtonFlags_EnableNav).
- B / Circle closes an open combo or menu and leaves a text field, and is
Escape otherwise, which the launcher's pages already go back on. Escape
now also closes the mod overviews and answers Cancel in the uninstall and
replace confirmations; closing an overview no longer also leaves the page.
- The right stick (the trackpad) moves a pointer that ImGui draws, and R3
(the trackpad's click) is the left button. The system cursor follows it;
a mouse move or a touch elsewhere takes the pointer back.
- The ImGui Win32 backend's own gamepad code (slot 0 only) is compiled out
so it cannot fight the launcher's events; nothing else used it.
Skater slot, menu and throwdown fixes; MY MODS rework; lobby parties
Saved skaters (Extension/Customization, Extension/Skater)
- The selected slot, and slots before it, came up as the standard
skater after a start or map change. The game asks for a slot's saved
outfit once per level, and that happened before the cosmetics catalog
could be read. The first selection now waits for the catalog (60 s at
most), and later map changes check against the catalog already read.
- The retry from abdda77 is removed: the record it wrote to is a
temporary on the game's stack, so it could never work.
Pause menu (Extension/UI/NativeMenu)
- ReSkate's entries stopped working after enough map changes: each
level change took new slots in a fixed table of 1024 for the same
buttons. A button now keeps its slot, and a full table hands out the
slot that has gone longest unused.
Throwdowns (Extension/Throwdowns, Runtime/runtime_init.cpp)
- Multiplayer crashed when someone placed a throwdown after a map
change. The event types are freed with the level but were cached for
the process. They are now cleared when a level is left, checked
before use, and looked for again in each level.
MY MODS (Launcher/gui_mods.cpp, gui.cpp)
- Search, a filter and an order; a switch per mod; a menu per row.
- Multi-select with tick boxes, Shift-click and Ctrl+A, and a bar to
enable, disable, move, update or uninstall everything ticked.
Lobby parties (Extension/Multiplayer/Session)
- A lobby is no longer one party. Its host keeps parties the way a
dedicated server does: invite, accept, join, leave, kick, promote,
open and close, with "/p" party chat. The "Lobby party" switch is
gone. Protocol version unchanged (38).
Mod merge (Engine/Vfs), follow-ups to e715b9b
- Files rewritten into the merged patch are tracked by position, so
another mod's added assets no longer shift the lookup.
- Two mods adding different assets under one name is now noted, with
whose copy the maps get.
- mod_merge_unshift_tests compiles with the launcher tests on.
A blank launcher window (Launcher/gui_renderer.cpp, Launcher/gui.cpp)
Reported as a launcher that showed nothing, from someone with a GT 1030.
The log named the card it had used: Intel HD Graphics 4600, the onboard
chip beside it.
- D3D12CreateDevice(nullptr, ...) takes whichever adapter DXGI lists
first, which on a PC with onboard graphics beside a real card is the
onboard one. It now walks EnumAdapterByGpuPreference with
HIGH_PERFORMANCE, skips software adapters and takes the first that
gives a device, as a game would. The old default adapter and WARP
remain behind it, so no working setup loses its device.
- That chip's Direct3D 12 driver cannot build a pipeline, and the ImGui
backend creates one on the first frame while ignoring whether it
worked (imgui_impl_dx12.cpp:792). Every draw then does nothing and the
window shows only ClearRenderTargetView's colour, which is near black.
Untextured shapes sample the font atlas's white pixel, so a failed
atlas looks identical -- the drawn background disappears with the
text. The pipeline and font texture are built in init now and checked,
and a failure says to update the driver and that the card may be below
what ReSkate and skate. need.
- None of this was diagnosable, because the renderer logged nothing at
all. It now records the adapter, its memory and whether it is the
software fallback; the window size, scale, system DPI and work area;
the font atlas size and whether it built; and the first frame drawn.
Those four lines are what turned "blank window, healthy log" into the
two bugs above, and they separate a missing device from a missing
pipeline from a window that draws and is still black.
World layers (Engine/Vfs/world_layer_scan.cpp)
- CoreGameAssets had a row like any other, and switching it off took the
world away with no clue which row had done it. essential_layer keeps
its node, so its children are still found and still switchable, and
drops its row -- which removes it from the menu, from "world <key>
off" and from layer sync at once, rather than hiding it in one place
and leaving it reachable from another.
- Choices are stored by row key, not by index, so no saved setting
shifts, and an "off" already stored for it has no row to apply to: a
profile that turned it off fixes itself on the next launch.
- The cache stamp goes to 2, so a cache written before this is rebuilt
instead of keeping the row alive on existing installs.
- The scan test asserted one row per node, which this breaks on purpose.
It now checks the counts differ, that no row's key holds
"coregameassets", and that the node is still there. Against a real
install: 262 rows to 261, six maps, passing.
The mod browser was a 960x620 panel inside a 1180x680 window, reached from
a tile the same size as SETTINGS. People could not find it, and once they
did there was room for about six mods.
Window
- The design size is 1440x840. Everything is laid out in design units and
multiplied by g_scale, which now scales down when that would not fit the
screen's work area, so a small laptop or a high DPI no longer gets a
window running off under the taskbar.
- The MODS tile is MOD MANAGER: taller than SETTINGS, directly above PLAY,
with the pending update count as a pill on its subtitle line.
- background.jpg is 1920x1027, so the bigger window is not upscaling a
1280x720 photo.
Mod manager, a page rather than a panel
- begin_page fills the window and draws its own minimise and close, since
it covers the main screen's. BACK or Escape leaves it.
- A nav rail holds MY MODS and GET MODS (count badges, blue for the open
one), the install actions and BACK. Refresh sits with what it refreshes,
top right of the content.
- Every mod is a full-width row: icon, name, author, description, version
and downloads, with a blue INSTALL or UPDATE and a tick box on the right.
Ticking several installs them in one run.
- Clicking a row opens that mod's overview in a popup, replacing the
side-by-side details pane that halved the list's width.
- virtual_rows draws only the rows a scrolling child actually shows.
ImGuiListClipper cannot do this, as it assumes every row is a fixed
height.
- Installing is a centred panel over a dimmed page with the activity, a
striped bar and Cancel, instead of a progress bar in the footer.
Settings
- The same page and rail: GAME, DISPLAY, KEYS and ADVANCED are nav tiles
rather than a row of tab buttons, and the content keeps to a 760 px
column so the explanations stay a readable measure.
- The Steam sign-in, Steam Guard and QR dialogs stay centred panels. They
are prompts, not pages.
Status and failures
- The STATUS tile carries its state in a coloured edge and a pill (WORKING,
READY, ACTION NEEDED, PROBLEM), its headline is 22 pt over muted detail,
and the standing facts (game build, ReSkate version) sit under a rule
instead of competing with the live status as equal rows.
- Launcher::fail keeps the raw message for the log, and problem.h turns it
into a headline and the next thing to try. The injection and PE-identity
failures ("VirtualAllocEx failed", "Injected ReSkate.dll is not present
in the child module list") all become "ReSkate could not attach to
Skate", with the anti-virus and overlay advice that actually fixes it;
the Steam download, a missing or damaged ReSkate.dll, the content cache
and a shared menu/console key each get their own. Anything unmatched
keeps its raw text.
- A failed tile grows Open logs and Copy details. Copy details puts the raw
message and the versions on the clipboard, ready to paste into Discord.
Mesh streaming pool (fixes loading crashes on older GPUs)
- 1.0.0 forced MeshStreaming.PoolSize to 3.5 GiB on every card. An RX 580
crashed while loading with DXGI_ERROR_DEVICE_RESET, and the oversized pool
is the likely cause. The pool is now a quarter of the largest hardware
adapter's dedicated video memory, capped at 3.5 GiB; cards of 6 GB or less
keep the game's own 520 MB. The card, its memory and the chosen size are
logged once at startup.
Remote players (busy servers)
- Transform interpolate is inline plain arithmetic: 22 -> 5.8 ns per joint.
It was 4% of a busy server's client frame, since every player within
200 m has the whole skeleton interpolated every frame.
- Players out of the camera's view and more than 8 m away are sampled at
20 Hz, like far players. Their position still updates every frame for
collision, sound and nametags, and a player coming into view is sampled
that frame.
Expression hooks (run for every expression the game executes)
- No profiler zones in the expression hooks: taking a sample switched them
on, inflating the very samples that measured the hooks.
- The throwdown lab pump does one sorted lookup of the running graph and
skips everything else for graphs it doesn't watch (graph_throttle gets
throttled_graph for this).
Settings and memory reads
- Script gameplay-setting and native-setting gets are served from small
per-thread caches, keyed on the asset/key address, its first 8 name
bytes and a new profile::Store::changes() counter, instead of a store
lookup each call.
- Per-frame reads in the client tick, native settings and population
controls use memory::peek instead of ReadProcessMemory.
- Script error logging probes each argument pointer once.
Launcher
- "Close the launcher when Skate starts" now hides the launcher and exits
when Skate.exe does, instead of exiting as the splash opens. Outside a
Steam library, Skate's protection closed the game (TerminateProcess 0x32)
when the launcher exited during startup; players confirmed turning the
setting off avoided it.
- STATUS tile warns when Steam isn't open or signed in that PLAY starts
offline as Unknown Player (hidden when Offline mode is chosen or while
launching).
- Setting description updated to match.
Startup interventions (20260929)
- exit_return 0x87755f2 -> 0x87fe7fb: the last LEA RSP,[RSP]; JMP stub
before the PUSHFQ / LOCK CMPXCHG routine, as 0x87367cf was in 20260908,
and the return address player logs show. Only enforced once a frame site
is armed; this build's protector has no equivalent rep stosb frame clear.
Client frame cost (profiled 2026-10-02)
- Transform interpolate is inline plain arithmetic (~1% of an MP frame).
- Script gameplay-setting and native-setting gets are served from small
per-thread caches keyed on asset/key address, name prefix and a new
profile::Store::changes() counter, instead of a store lookup each call.
- Hot-path reads in client tick, native settings and population controls
use memory::peek instead of ReadProcessMemory.
- Script error logging probes each argument pointer once.