Commit Graph
136 Commits
Author SHA1 Message Date
shumsic 845e4cb6ba Multiplayer: check a player's distance when their first pose places them, before spawning a skater
### Problem
1. **Unplaced first-pose phantom spawns (`render`):**
   In the 500 ms distance loop (`s.next_shown_rank`), `p.shown_wanted` is set to `everyone || !p.placed || nearest_distance(...) <= ...` so that before a peer's first pose arrives (`!p.placed`, `sampled == false`), their status reads `"Waiting for player poses."` rather than `"Further away than players are shown."`.
   However, when their first pose packet arrives in `each_active_peer` (`const bool sampled = ...`), `if (sampled) p.placed = true;` flips `p.placed` to `true` while `p.shown_wanted` is still `true` from when `!p.placed` was true. Because `nearest_distance` has not yet been checked against the newly populated `p.render_pose`, `show_remote()` is immediately called on that frame for every newly-placed player on the server — spawning a full 395-bone 3D skater, 16-bone skateboard, and cosmetics for players 300–500 m across the map, only for the next 500 ms `s.next_shown_rank` pass to evaluate `nearest_distance > s.player_distance` and immediately destroy them (`remove_remote()`).

2. **Follow-camera orbit shifting the distance boundary (`nearest_distance`):**
   `nearest_distance(p, local, view)` takes the nearer of `local.pose.root.position` and `view` (the camera). While `view` is needed when the camera is detached from the local skater (spectating another player across the map, replay camera, or flying a drone), during ordinary skating the follow camera orbits ~3–5 m around the local skater. Rotating the camera around the skater shifts the effective `player_distance` boundary by ~8–10 m (consuming the 15% hysteresis band at 50 m) and causes clusters of players near the boundary (such as a Spot Battle crowd) to spawn and despawn as the camera turns.

### Solution
1. When a peer's first pose transitions `p.placed` from `false` to `true` (`if (sampled && !std::exchange(p.placed, true))`), immediately update `p.shown_wanted` from `nearest_distance(p, local, view) <= s.player_distance` before evaluating `spawning` and `show_remote()`. Peers before their first pose still keep `p.shown_wanted = true` (`"Waiting for player poses."`), nearby peers still spawn on their first pose frame without waiting for the 500 ms tick, and far-away peers never phantom-spawn on arrival.
2. In `nearest_distance()`, consider `view` when `!local.ready` or when the camera is more than 12 m from `local.pose.root.position` (spectating, replay, or drone), keeping the distance radius anchored to the local skater during normal third-person follow-camera rotation.

### Verification
- Tested in-game in 44–78 player online lobbies with active Spot Battle throwdowns (`ReSkate.dll` runtime verification).
- Verified in `ReSkate.log` that far-away peers no longer trigger `creating a remote skater` / `Skater component factory` spawns on their first pose packet and that rotating the camera toward a Spot Battle huddle near the 50 m / 70 m boundary no longer triggers a spawn storm.
2026-10-26 15:54:44 -04:00
AyeZeeBB 8e5fb74498 Multiplayer: a remote skater whose parent link is one byte short is put right and destroyed, instead of crashing at map exit
2.1.2 left such an actor alive and forgotten. When the level went, the game released its parent
through the same pointer, took one off the byte below the parent's vtable pointer and called
through it (Skate.exe+0x2b8092f and +0x2b7fcfb, every report after the "link ... is damaged"
warning with the same bus). A link exactly one less than the parent the bus also holds at +0x48 is
now repaired before the destroy; other damage is still left alive. What shortens the link is not
known yet.
2026-10-24 22:47:02 -05:00
AyeZeeBB e0d85cae11 Multiplayer: another player on a Studio map's own surface sounds like the default surface instead of crashing the game
The skater sound state sent the packed material words of the surfaces under a skater as they were.
A Studio map's surfaces are appended to the root grid from slot 116 on, numbered by each install's
own set of map mods, so the same word named another surface on the other side, or a slot past the
end of its grid, and the game's material lookups (0x7111c0, 0x45e120) read past MaterialIndexMap
(Skate.exe+0x41da7b and +0x6fc93b, 240 installs in a day on 55a7342, on Tokyo 2026, Community
Center, Olay Way and other maps with their own surfaces). Those words now cross as the grid's
default, when sent and when received, so older clients sending their own numbers are covered too.
2026-10-24 22:47:02 -05:00
zeex64 1d80e77b11 Merge pull request #362 from mauwpix/pose-own-values
Fix skater.pose changes being applied twice (jittering avatars)
2026-10-24 21:53:10 -05:00
mauwpixandClaude Opus 5.5 c7457c10a1 Skater: plugin poses apply once a frame instead of twice every third frame
skater.pose changes the skater relative to the game's animation. Some of those changes were applied
twice every third frame, so a posed skater shook in a three-frame rhythm, and their board with them.
The more a plugin changed, the worse it got: an avatar with the hips scaled and offsets on most of
the body needed workarounds in its plugin to hold still. Now every pose is applied exactly once, and
a plugin can give skater.pose the values it means.

Why: ReSkate draws a pose by writing it into the game's bone buffer just before the render, and the
game evaluates the next frame from that same buffer without overwriting all of it. Two of three
frames it resets the buffer itself; in the third the pose drawn was still there and the next pose
went on top of it. Now the game's own values go back as soon as the renderer has its copy, so the
game, the body physics and the next animation update only ever see the game's pose.

This replaces own_scale, the earlier fix for the same cause (the Filmer's forearm at 0.001 shrinking
to nothing). It covered only scale, and put it back after the game had already run on the posed
values, so rotations and offsets still doubled. The new way covers every part of every joint, for
pose, carry and reach, with nothing to track per joint and nothing to clean up after a pose ends.

Checked in the game: the avatar with the hips at 0.667 and offsets on 48 joints holds still with
only skater.pose (shoulder distance 0.117 m every frame, where it was 0.117 0.117 0.067 ...). The
Filmer 1.0.0's camcorder preview looks and measures as on main, and the skater is back to normal
after it.

Details: the renderer copies the bones in submit_render_pose (0x4c7e7a0), which the render pose
caller (0x2c20d99) calls right after render_pose with the same render data; found with a guard page
on the bone buffer that logged every access with its call stack. native_skater hooks it and, for the
render data the pose override just wrote, calls the new set_pose_override_drawn_listener.
skater_animation keeps what on_render wrote over and puts it back in on_drawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 02:31:13 +01:00
AyeZeeBB e47c91d13a Multiplayer: applying mods in a session reloads your own map and keeps you in it
The reload after a live apply was a console "load", which in a session either ended it (a guest's
map going away is "your local map changed"), was refused (a guest without map rights), or moved
everyone (a server admin's load changes the server's map; a lobby host's reload sent every guest
through it too).

Now client_tick tells the session first (multiplayer::begin_local_reload) and loads the same map
locally with no multiplayer rule (queue_local_load). The session keeps the reload as travelling in
the same world: a guest keeps telling the host or server it is loading (world_ready, so it does not
time out) and that it is back; a host says it is not in the world for that long, so its guests wait
instead of moving, and nothing is a host map change. A reload refused by the loader is taken back
(cancel_local_reload); one that never starts ends after 20 s; in no state to reload (changing maps,
joining) the mods show at the next level load. The dedicated server already handles a player
reloading on their own.
2026-10-24 19:56:09 -05:00
AyeZeeBB 55a7342dc6 Multiplayer: dedicated servers require plugins, and players get them and the map on one card
A server lists Thunderstore packages in plugins.required ("Owner-Name", or "Owner-Name-1.2.3"
for at least that version, at most 16). It checks each on Thunderstore as it starts and leaves
out one not listed as a plugin, and sends the rest with its map offer (protocol 55).

A joining player's game checks them before the server's map loads. Those missing, switched off
or too old go on one card with the map when that is missing too, so the player is asked once.
GET ALL downloads and installs each, switches on any that were off, runs one live merge when
any brings game data, and joins; CANCEL leaves. The game fetches a package only when
Thunderstore lists it as a plugin and it installs as one (a reskate-plugin.json naming Luau
code), whatever a server asks; anything else is taken out again, so a cosmetic can never be
required.

map_download handles a list of packages instead of one map, and the Thunderstore chooser takes
the category it needs. A map alone keeps its card as it was.
2026-10-24 12:00:09 -05:00
AyeZeeBB 8ae812a10c Multiplayer: a player using the controls counts as active on a dedicated server, so flying a drone is not away
The server took a player as away unless their skater moved or they chatted, talked or changed
objects, so a pilot of the FPV drone plugin, whose skater stands still, was removed by the away
limit. The game now notes when the player works the controls (keys, mouse, a gamepad's buttons,
sticks and triggers, a joystick a plugin reads) and tells the server every 20 s while they do,
with a new presence packet (protocol 54). The server counts it as activity, once in 10 s at most.
2026-10-23 14:29:05 -05:00
AyeZeeBB 118c489fdb Plugins: what the Filmer needs as a plugin: carry, who sees a pose, a step in the camera cycle
PR 343's Filmer becomes a Luau plugin mod (kept in contrib/plugins/filmer) on general APIs instead of
its own C++:

- reskate.skater.carry: something held out in one or both hands and aimed, the body crouched (feet
  kept) and hunched over it, turned on the spot after it (Extension/Skater/carry.cpp, from the PR's
  filmer_hold.cpp, with tests)
- shown = "everyone" | "others" | "me" on skater.pose, reach and carry: the pose sent to the other
  players and the one drawn here are split when asked
- reskate.camera.cycle and the camera_cycle event: a plugin's camera as the last step of the camera
  height button's cycle, after first person
- reskate.session.nametags(false) holds the names over the other players off
- board.hide(bone, {others = true}) hides it in the board the others are sent too (a sent-pose filter)
- outfit.set takes hand wear slots; input.bind takes an action with no key of its own
2026-10-22 18:46:49 -05:00
zeex64 c745d605ae Merge pull request #310 from moelrobi/fix/poll-answer-repeat
Polls: answers no longer refused as repeats, and less chat noise
2026-10-21 18:45:38 -05:00
AyeZeeBB b67fe93e55 Crashes: guards for 2.1.1's top crash sites, launcher checks, stuck-load reports, quieter logs
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.
2026-10-21 18:10:58 -05:00
AyeZeeBB 55d5f70d4f Plugins: touching other players, hiding one on your screen, and a raycast that hits players
reskate.session.touch, touching, hide, hidden and raycast, and the touch and touch_end events.
The session publishes every skater's body as capsules while a plugin reads them; a hidden
player goes through the same path a throwdown's hidden players do.
2026-10-20 18:06:30 -05:00
AyeZeeBB 41b5973896 Plugins: the character room and typing into a drawn field, and Animator Studio on both
- 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.
2026-10-20 17:43:40 -05:00
AyeZeeBB cf5165b791 Plugins: the general APIs a filming mode needs
- reskate.input.hide hides the right stick and the stick clicks, and reskate.input.mouse reads the mouse's movement and wheel.
- reskate.skater.cruise: cruise control up to a speed, optionally easing down to it.
- reskate.skater.reach and hand: an arm reaching for a point (two-bone IK, Extension/Skater/arm_reach), in the pose drawn and sent, and where a hand joint is.
- reskate.entities.set's attach: an object kept on a joint of your skater or another player's, moved every frame.
- reskate.screen.look and slap: the Filmer branch's VX1000 post-process as a fixed-parameter camcorder look drawn by the overlay.
2026-10-20 15:24:00 -05:00
waguandClaude Opus 5.5 cd36529865 Merge plugins into server-lua-api
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>
2026-10-20 12:13:19 -04:00
wagu ded49756ea Docs: project structure for clarity 2026-10-20 09:59:50 -04:00
waguandClaude Opus 5.5 67e8e439f7 Server: lock ollies for a player
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>
2026-10-20 09:53:31 -04:00
AyeZeeBB 8444553402 Throwdowns: the throwdown kit, so a mode is a plugin and the SDK knows no particular mode
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.
2026-10-20 01:04:54 -05:00
AyeZeeBB 37516e24fc Throwdowns: a plugin mode's flag is offered to the session, joined with the game's own Join
When a plugin mode's flag goes down in a session, the relay offers it to the other players
as that mode's: a new mode message (kind 17) names it, its flag and its player count, and
its offer's series is one older versions do not show. A player whose plugin has the same
mode gets a copy with the mode's own flag mesh and joins it natively; the copies are held
at ten players so none fills and starts a Spot Battle, and the mode's player count decides
who may still join. throwdown_mode events now carry host, leader, player and players: the
host hears joined and left, a player in another's flag hears joined, left, closed and
start, and start names who plays, the leader first. The plugins run the match from there.

A mode can take a stock throwdown's place on the picker (replaces = "skate", "spot_battle"
or "jam"), so his R.E.S.K.A.T.E. sits where S.K.A.T.E. does.
2026-10-19 19:04:11 -05:00
AyeZeeBB 2d3b77701d Plugins: drone, Board Break and Road Rash are plugin mods now; their C++ is gone
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.
2026-10-19 16:22:15 -05:00
AyeZeeBB 5c0993afea Plugins: the wider set (phase 3), behind one Game interface the tests fake
- Skater: skater.state() and the skater_state and bail events (events.skater); noclip and boosts
  through the console's own queue, under the host's rules (skater.control); a save point of the
  plugin's own (trainer); the game's HUD hidden while any plugin asks (hud).
- World: park editor objects listed, placed, moved and removed through the editor's own edits and
  the session's object rules (world.objects); time of day, world controls and park rotation as
  the World menu sets them, refused where the host chooses (world.environment); fast travel and
  levels, refused for guests (travel); AI skaters, outside sessions only (world.ai).
- Session: other players' skaters as last heard (session.skaters, a feed the session publishes
  each frame, plugin_session_feed.h); a plugin's tag above a player's nametag (session.nametags);
  voice volume (voice.control); chat lines as events (chat.read) and sending one, one every 3 s
  (chat.send); throwdown state and events (throwdowns).
- Music from outside players (music), progress (progression.read) and game settings
  (settings.read), read only; named actions on keys or pad combos, pressed while the game has the
  input (input); full-screen screens with the input, closed by Esc (ui.screen).
- Libraries and events between plugins: expose, require_plugin, emit, on_plugin; plain data is
  copied across VMs and a plugin in a call cannot be called back into.
- net.http: HTTPS to the hosts reskate-plugin.json's new "domains" lists; no redirects, cookies
  or Windows sign-in; private and local addresses refused; 10 a minute, 4 waiting, 1 MB back.
- Left for engine work (listed in the design doc): respawn and freeze, manuals, the camera, trick
  animations, replays, starting throwdowns, the game's own soundtrack, overlay images, rebinding
  plugin keys in the Binds page, local-only chat lines.
- reskate-test has a wider module (ptest noclip, boost, hidehud, object, night, day, ai, screen,
  bind, music, chat, web by hand); reskate-hostile has chat-spam, edit-spam, travel-spam,
  web-elsewhere, bind-spam and screen-loop.

All host tests pass; not yet tried in game.
2026-10-19 13:49:29 -05:00
AyeZeeBB d884c1c161 Plugins: the rest of the core set (overlay, menu panels, sounds, mods, session and messages)
- overlay: reskate.on("draw", function(d) ...) each tick, with text, rect, line, circle, poly
  and world_text, world_line, world_circle (projected through the camera as the frame draws),
  size and world_to_screen; 4000 shapes a frame. reskate.ui.toast through ReSkate's notices, three
  in 5 s. Images are left for later: they need texture slots in the overlay's renderer.
- menu: reskate.menu.panel(title, build) on a new PLUGINS tab of the ADDONS page, with heading,
  label, separator, button, toggle, slider and dropdown, built ten times a second while it shows.
- audio.ui: the game's menu sounds by name, ten a second. mods.read: installed(name) and list(),
  from the discovery thread's scan.
- session.read: active, is_dedicated, players, me, host, and player_joined / player_left.
  net.session: send(to, message) to the same plugin on other games (by its manifest.json name) in
  the session's throwdown packets, tagged 0xD1 (plugin_wire.h); 4 KB each, 16 KB a second.
- The overlay and the host share plugin_ui.h (shapes, panels, actions) and plugin_camera.h.
- Fixed: the overlay asked ImGui for the window's size before its context existed, which crashed
  on the first frames; it now reads the size from the context.
- reskate-test has overlay (ptest hud), menu, audio, mods and session (ptest ping) modules;
  reskate-hostile has draw-flood, toast-spam, sound-spam, message-flood and panel-loop.

Checked in game: ptest all 62 passed, 0 failed; the test HUD, the PLUGINS tab and its panel; every
hostile attack stopped or refused, trick-loop and panel-loop included. Not yet: ptest ping with a
second player.
2026-10-19 13:20:43 -05:00
AyeZeeBB 8017c2feb3 Multiplayer: other players' objects are solid by default
A player made ghosts by the local player stays a ghost.
2026-10-18 16:33:52 -05:00
AyeZeeBB a64cd93d82 Drone: other players' drones are markers only, without a model
A model made for another player's drone crashed the game a few seconds
later in the per-frame update of its shader sections (Skate.exe+0x505b04b,
2.1.0): 28 of 29 reports there came right after one was made. Real
players' drones show as markers and are heard as before until that is
found; the pretend one from dronemodeltest remote still gets its model.
2026-10-17 20:32:28 -05:00
Robin MoellerandClaude Opus 5.5 07cd31b8aa Votes: the same answer in the next poll is no longer turned away as a repeat
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>
2026-10-18 02:02:56 +02:00
AyeZeeBB 93528884b3 Park editor: a session with object scaling off keeps saved scales without resizing
On a dedicated server with object scaling off, a loaded park's scaled objects were resized anyway
(and after a refused resize the pose poll saved 1x over their scale), and a scaled paste, set or
undo ended in a resize the server would not take, so the edit waited on it. The placements now
know whether this game may resize (set by the session from the server's setting and the role, the
host and roles that may always build exempt, allowed outside a session): where it may not, objects
stay 1x in the world, nothing waits on a resize, and the layout keeps their scale for when scaling
is allowed again. A transaction test pastes a scaled object with scaling off.
2026-10-17 13:26:16 -05:00
AyeZeeBB d9aee11957 Drone: dronemodeltest remote flies a pretend other player's drone, and a missing model says why
The pretend drone goes into the beacon as a received one would, so other players' drone models
and markers can be checked without a second player. A drone flying 3 s without its model now logs
what is waited for (the engine, the local skater, or the thread's job context) instead of waiting
silently.
2026-10-17 10:41:31 -05:00
AyeZeeBB 320086381c Paths: the last few written as text go through UTF-8, so a non-ASCII name no longer throws
path::string() uses the ANSI code page and throws on a character it lacks (a Cyrillic folder on
an English Windows). The remaining places it was used now go through path_utf8:

- Mods: a deck folder or a mod folder named outside the code page no longer leaves the Board Break
  decks empty, or the Mods folder ignored altogether.
- Game and mod archives: a file under Win32 with such a name is passed over instead of stopping
  the read.
- pose-dump no longer ends the multiplayer session when the game folder is not ASCII.
- The item browser's status line and a Board Break warning no longer fail on such a path.
- Launcher: system fonts are named to ImGui in UTF-8, which it reads.
2026-10-16 18:12:34 -05:00
AyeZeeBB 9a011c0417 Drone: the pilot's own motor plays through the game's sound
In a level the pilot's drone is played with the other players' drones through the
game's positioned voice sound, just under its camera, so it gets the game's mixing,
reverb and obstruction. The Windows output only plays it where the game's sound is
not there to take it.
2026-10-16 16:43:56 -05:00
AyeZeeBB eeeb751f1b Drone: other players' drones are heard where they fly
Each remote drone's motor is synthesised and played through the game's positioned voice sound,
the one proximity chat uses, so it is placed in the world and muffled behind walls. The throttle
is worked out from how the drone accelerates, so the drone packet and protocol stay the same.
Full volume only within 1.5 m, heard out to 120 m; the DRONE page's sound volume sets the level.
2026-10-15 22:39:55 -05:00
AyeZeeBB 0dcbcb3a89 Drone: other players' drones shown as a 3D model
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.
2026-10-15 22:39:49 -05:00
AyeZeeBB 46e18900a1 Multiplayer: a sender can no longer flood a player's memory with pose and sound streams
A whole pose from a dedicated server opened a stream under any of its
65,536 numbers, and each stream kept its last 32 poses. Streams were only
dropped once their player had left, so a sender that opened one under
every number for a player who was there (itself) made the receiver keep
millions of poses. Sound streams were open the same way.

- pose_batch::open_stream: a player's new stream ends their old one, and
  no more than 512 streams are held (twice the player limit); both the
  pose and sound receivers open streams only through it.
- A stream is opened only for a player in the session. A refused whole
  pose is not acked, so a real server sends it again; sound is sent whole
  every couple of seconds.
- Tests: the reported flood leaves one stream for one player and 512 for
  65,535; the same for sound, and only admitted players open a stream.
- mesh_tests: HookBatch stubbed, which the session uses since the freeze
  fixes; the test had stopped linking.
2026-10-15 20:02:03 -05:00
AyeZeeBB 5e43dc2871 Multiplayer: a chain of resized bones is held to the bone-scale limit as a whole
Each bone's scale was held to the limit (a server's bone-scale, 2x by
default; 8x on every client), but a bone shows at its own size times its
parents' and the whole skater's. A modified client resized each bone of
a chain to the limit: four neck bones at 2x showed a neck 16 times as
long, within every per-bone check.

- skater_bone_parents.h: the parent of each of the skater rig's 386 bones,
  read from the game's data (animbase_default_renderskeleton), which the
  pose's skater bones follow.
- limit_bone_scale then holds each bone to what its parents leave of the
  limit: the size it shows stays within 1/limit to limit. A single resized
  bone (a bigger head) is unchanged; bones past the rig's have no parent
  and keep the per-bone limit.
- Servers and clients already hold every pose to their limit with it, so
  the combined limit follows each server's bone-scale setting, and every
  client's 8x where a server allows more.
- Tests: a neck chain at 2x each shows at 2x; a resized skater's bones are
  held to what its own size leaves them.
2026-10-15 19:00:37 -05:00
AyeZeeBB 719cba1166 Freezes and crashes: the game's threads no longer wait on ReSkate's slow work
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.
2026-10-15 18:10:53 -05:00
AyeZeeBB 94a616feb9 Map downloads: one that has begun goes on when the session stops waiting
Follow-up to #225. The session still waits 30 minutes in all for a map
(checking, the two-minute question, download, install, apply, join), and
servers and lobbies still let a player go at 30 minutes. But a download
that has begun is no longer cut off at 27 minutes: a large map on a slow
connection never came at all that way.

- Once the download has begun, the session's deadline is the session's
  alone. The download goes on (up to its own four hours, as before #225),
  installs and is applied, and the player is told to join again; no
  rejoin is tried past the deadline, where the server would turn them
  away. "Map still downloading" says so when the session stops waiting.
- Before the download, the deadline still ends the check and the question.
  Declining or cancelling still stops it and removes the partial file.
- A download whose session has gone is not cancelled by joining another
  session; a new map need is turned away until it is done.
- The 27-minute transfer budget and its completion reserve are gone from
  map_fetch_policy.h, with their tests; the map download tests cover the
  new behaviour.
- The HTTPS timeout test compiles with warnings as errors (a signed and
  unsigned comparison), and get_text's stale-file removal is indented again.
- README: map downloads described as they now are.
2026-10-15 14:23:06 -05:00
zeex64 e7c6df0df6 Merge pull request #225 from Jean-LucTch/dev
Fix multiplayer custom map download and join timeout handling
2026-10-15 14:03:06 -05:00
zeex64 67d84d5bd7 Merge pull request #227 from neekgate/fpv-drone-pr
ReSkate FPV Drone
2026-10-15 13:20:11 -05:00
zeex64 32a83d429e Merge pull request #241 from Kalmarv/pr/command-crash-fix-v2
Console: mp map-markers, mp puppet-savings, throwdown-hud and throwdown-offboard no longer crash when typed alone
2026-10-15 13:19:12 -05:00
Fourth Studio b999f37e7b Board Break: wear and tear, rail snaps, break and body sounds, settings
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.
2026-10-15 19:22:21 +02:00
KalmarvandClaude Opus 5.5 e5557d914e Multiplayer console: map-markers, puppet-savings and the throwdown switches work by themselves
mp map-markers, mp puppet-savings, throwdown-hud and throwdown-offboard take an optional
argument and, without one, say what they are set to. Each read values[0] unchecked, and an
optional argument that is not given adds no value, so typed by itself each indexed an empty
vector. They check now, as mp collision and challenge-invite beside them do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:56:47 -06:00
AyeZeeBB 6c45e9666d World layers: a server's catalog must be the supported build's, and a player is not put out over one that is not
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.
2026-10-13 18:45:37 -05:00
AyeZeeBB f356c1eafd Music screen: large playlists no longer empty it, and two logs say why things fail
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.
2026-10-13 17:38:50 -05:00
AldoandClaude Opus 5.5 57d98307a7 Skater: FPV drone mode
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
2026-10-13 21:59:49 +01:00
AyeZeeBB 9b81c821e4 Kicks: a popup for the player who was put out, and vote kicks last 5 minutes
A player kicked or banned only found out why from the status line of the
multiplayer menu, if they opened it.
- What the host or server says as it puts a player out ("You were
  kicked...", "You are banned...", away too long, a mod or cheat it does
  not allow) is now shown in the middle of the screen with an OK button,
  under YOU WERE KICKED, BANNED or REMOVED. The same when they are refused
  on trying to join again early. A link that is simply lost shows nothing
  new.
- Answered with A or B on a controller, Enter or Esc, or a click on OK. The
  game's input is held while it is up, as for the vote for the next map, so
  the button that answers is not also a press in the game.
- Console: "popup test" shows it with a sample message, to try it without
  being kicked.
Server: a kick by a vote has a time of its own, votes.kick_minutes, 5 by
default (1-1440, or 0 for until the server restarts); it no longer goes by
players.kick_minutes, which is now an admin's kick only and is still until
the restart by default. Console: votes kick-minutes <minutes>|restart, and
"votes" shows it. A config without the key takes the 5 minutes.
2026-10-13 15:38:36 -05:00
copilot-swe-agent[bot]andJean-LucTch 80242c0ec8 Cover deadline boundaries, safe cancellation and fresh join races
Co-authored-by: Jean-LucTch <88255067+Jean-LucTch@users.noreply.github.com>
2026-10-13 11:27:08 +00:00
copilot-swe-agent[bot]andJean-LucTch 8eddcab142 Coordinate custom-map downloads with the multiplayer deadline
Co-authored-by: Jean-LucTch <88255067+Jean-LucTch@users.noreply.github.com>
2026-10-13 11:11:27 +00:00
AyeZeeBB 28118efc27 Objects: other players' objects are ghosts by default, and their card is a visitor's
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.
2026-10-13 03:37:45 -05:00
AyeZeeBB 180cdc9c85 Objects: the placed-object prompt names who placed the object 2026-10-13 01:04:13 -05:00
AyeZeeBB 28231c0df2 Typing dots: none for commands and private messages, and no flood of stop notices
A line that starts with "/" is a command, a whisper or party chat, so typing
one no longer shows dots to the whole server. Dots already showing are cleared
when the line becomes one.

A "stopped typing" notice was relayed every time it arrived, while "typing"
was held to one per 200 ms, so a modified client could flood everyone through
the host. A lobby's host and a dedicated server now pass a stop on only when
that player's dots are up, which is at most once per accepted "typing".
2026-10-12 23:45:27 -05:00
zeex64 58231d4ead Merge branch 'main' into typing-dots 2026-10-12 23:40:24 -05:00