Commit Graph
673 Commits
Author SHA1 Message Date
hypnomalice dd74399e8a Shaders: validate retained table bounds before proving stock preservation 2026-10-25 08:23:28 +00:00
hypnomalice 00aef90a34 Mods: retain bundle-owned custom programs alongside verified stock aliases 2026-10-25 01:34:24 +01:00
hypnomalice 744ced7209 Tests: audit bundle shader graph ownership and surface excluded material rows 2026-10-25 00:52:57 +01:00
hypnomalice ea13e8c22c Mods: preserve the selected bundle shader graph instead of importing foreign heaps 2026-10-25 00:09:14 +01:00
hypnomalice 0a145850ba Mods: merge independent shader graphs and preserve additive variant bundles 2026-10-24 22:50:36 +01: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 e2d12a701d Launcher: plugin mods show as Plugin instead of Nothing, and UPDATE sits after the version it brings
The mod list now notices a reskate-plugin.json that names an entry, reskate-objects.json and
reskate-music.json, and mods::mod_contents lists what a mod holds, most telling first. The
launcher's Type column shows the first of them (with "+" when there are more, all of them on
hover), and the in-game Mods page joins them. A reskate-plugin.json with only variants or
resident assets is not a plugin.

The UPDATE button moved from its own spot before the Type column to right after the blue
old -> new version, cutting a long name short rather than pushing into Type.
2026-10-24 11:24:20 -05:00
AyeZeeBB b739861fdf Docs: macOS and music source setup move to the docs site instead of files in the repo
MACOS-SETUP.md and MUSIC-SETUP.md are now docs.reskate.dev/macos and
docs.reskate.dev/music-sources; the README links them.
2026-10-24 11:17:36 -05:00
zeex64 cd45a77843 Merge pull request #353 from moelrobi/plugins/server-visibility
Plugins: server plugins hide a player from others, or mute them
2026-10-24 11:10:21 -05:00
Robin MoellerandClaude Opus 5.5 dd8e610482 Plugins: a dedicated server's plugins hide a player from others, or mute them
Hide and Seek, tag, spectating and ghost runs need one player to be
gone for some others, which no plugin could do.

reskate.server.hide(target, viewers, {skater, name, voice, sound}) and
reskate.server.unhide(target, viewers?) do it per viewer; viewers is a
player, a list of players or "all" (everyone on now but the target).

Wholly (the default) the target leaves those viewers' rosters, so their
games take the skater, its name tag and its objects away as for a player
who left, and the server sends them nothing of the target: poses, sound,
voice, outfit, effects and objects. No protocol change: the roster per
viewer is the one the cards and polls use, and the server's introduce
already holds a player back until shown. Shown again, the target is
introduced anew (outfit, then whole poses, then all its objects).

Without the skater (skater = false), only the voice and/or the sound is
held back and the player stays. Name and skater go together: a game has
no skater without its player, and hiding one without the other would
need a protocol change.

Refused while the target shares a party or a throwdown with one of the
viewers, whose game would lose a party member or a player mid-way; while
hidden, the two cannot come into one party. A plugin that stops shows
everyone it hid; a player who leaves is dropped from every list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYabE7FCPgj3SaWeF92EML
2026-10-24 12:26:31 +02:00
AyeZeeBB 194942779c Plugins: a dedicated server's plugins hear chat, know the map, change it and set the time of day
A server copy on a dedicated server had no chat, an empty session.world() and no way to change the
map or the time of day, though the server does each with its own commands. Now:

- chat (chat.read): each player's line as the server passes it on, the server's own lines to
  everyone, and a /command the server does not know itself, which goes to the plugins instead of
  "Unknown command" while one hears chat, so a game mode takes its own commands;
- session.world(): the server's world and the name of its map;
- travel.levels() and travel.load(): the server's maps, and a map change as the console's map does
  (checked, saved and logged);
- world.set_time(): the time of day for everyone, as tod does, when the server has its world
  layers and layer-sync is on.

The names custom chat commands may not take now include every server command: lock, unlock, locks,
throwdown-locks, words, bone-reach and poll-run were missing, so a custom command could hide them.
2026-10-23 18:39:28 -05:00
AyeZeeBB 823dfc27a2 Server: the READMEs no longer list the drones setting and command
sync_drones and "drones on|off" went with the drone's C++ when the drone became a plugin; the server has neither.
2026-10-23 17:37:58 -05:00
AyeZeeBB 7fa407ac36 Launcher: GET MODS installs plugin mods and mod packs instead of refusing them
A Thunderstore install was refused unless the package held layout.toc, reskate-levels.json or a
parks folder, so a package with only a plugin, or a pack whose manifest only lists dependencies,
could not be installed from GET MODS. reskate-plugin.json now counts as content (and finds the mod
inside a package like the other markers), and a manifest with dependencies is a mod pack, as
InstallOptions::require_content always said.
2026-10-23 16:35:42 -05:00
AyeZeeBB b978853bab remove old drone model 2026-10-23 16:07:44 -05:00
AyeZeeBB 1b613b0dce Merge pull request #347 from snubroot/feat/mod-object-families
Objects: mods name their Quick Drop objects and group them into families with reskate-objects.json
2026-10-23 16:00:06 -05:00
AyeZeeBB 60cd8395d9 Merge pull request #351 from moelrobi/plugins/server-cards-content-w2s
Plugins: server cards and polls, mods.content on dedicated servers, world_to_screen bounds

Conflicts in the plugin and server plugin tests, where #348's role tests and this PR's tests were
added in the same places: both kept.
2026-10-23 15:56:02 -05:00
zeex64 85f6874090 Merge pull request #349 from ryanc2112/docs/troubleshooting-and-bug-template
Docs: a Troubleshooting section in the README, and a bug report template
2026-10-23 15:52:06 -05:00
zeex64 579bc87129 Merge pull request #350 from moelrobi/build/plugin-tests-linux
Build: the plugin tests build and run on Linux
2026-10-23 15:50:29 -05:00
zeex64 e34c4ab94f Merge pull request #348 from moelrobi/plugins/server-roles
Plugins: server plugins know each player's role
2026-10-23 15:50:02 -05:00
AyeZeeBB 2ae6c59d3f Console: the menu command is back, so a key or combo bound to the ReSkate menu opens it again
"bind reskatemenu" runs the menu command, which went with the drone's commands when the drone
became a plugin, so every press said "Unknown command" and a player without an Insert key had
no way into the menu. At startup the log now warns about a bind whose command is not registered.
2026-10-23 14:35:41 -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
Robin MoellerandClaude Opus 5.5 d6cf244c26 Plugins: a dedicated server's plugins show players cards and ask them polls
A server plugin could only reach players through chat lines (say, tell).
A mode that asks players something (which track next, join the next
round) had to read chat commands, and could not show a card.

reskate.server.card(who, text, seconds) and reskate.server.poll(who,
question, answers, {seconds}) put a card or a poll on the screens of a
player, a list of players or "all", in the slot the roster already has
for the server's announcement and vote, so games need nothing new and
the protocol is unchanged. Each player's is their own and shows over the
server's until it ends, then the server's shows again. A poll has one id
for everyone it asks; a player's answer ("/2", as the game sends it)
closes it for them and comes back to the plugin that asked as a
poll_answer event; a poll that runs out, is replaced by a newer one for
that player, is closed with reskate.server.poll_close, or whose player
leaves comes back as poll_closed with why. The plugin counts the answers.

Both need the "server" permission and a server copy on a dedicated
server; a lobby's host has no roster slot for them. Answering a plugin's
poll is not a server vote, so it does not ask the player's role for
"votes". A plugin that stops takes its cards and polls with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYabE7FCPgj3SaWeF92EML
2026-10-23 15:51:15 +02:00
Robin MoellerandClaude Opus 5.5 0706730e79 Plugins: mods.content and read_content read the server's Mods on a dedicated server
A server plugin with mods.read could call reskate.mods.content, but the
server's game had no content of its own, so it got nil and "not
available". A mode that keeps its data in mods (a race reading start
and finish from a park mod) could only read it in the players' games.

The server now answers from every mod folder in its Mods, by name, as it
finds its maps there; it has no load order or mods.json. A hidden folder
(.reskate_plugin_data) and names a plugin could not ask for are left out,
and a mod's title is its manifest.json's "name".

The listing and reading moved from plugin_game_runtime.cpp into
plugin_content.cpp, which the game and the server share, so both keep
the same limits: the folders the manifest's "content" names, 1000 files,
four folders deep, 1 MB a file, nothing outside the mod's folder. The
game's side is unchanged apart from calling it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYabE7FCPgj3SaWeF92EML
2026-10-23 15:50:55 +02:00
Robin MoellerandClaude Opus 5.5 f763792732 Plugins: world_to_screen gives nil for points the drawing functions would refuse
A point just in front of the camera and off to the side projects to
millions of pixels. world_to_screen handed that back, and drawing it
(d:poly, d:line, ...) raised "must be a finite position", so a client
plugin that drew a world marker near the camera was switched off after
five errors. world_to_screen now returns nil when x, y or the depth is
not finite or beyond 1e6, the same bound the drawing functions use, as
it already did for a point behind the camera.

The draw test now also checks that its handler ran to the end; before,
a failing assert inside it went unnoticed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYabE7FCPgj3SaWeF92EML
2026-10-23 15:50:55 +02:00
Robin MoellerandClaude Opus 5.5 4500184b48 Build: the plugin tests build and run on Linux
dingosdk_plugin_tests covers the plugin sandbox and every reskate.* API,
but it only built on Windows, so contributors without Windows could not
run it. Now it builds wherever DINGOSDK_BUILD_PLUGIN_TESTS is on. On
Linux the test target adds plugin_discovery.cpp and mod_list.cpp itself,
because the Linux plugins library leaves out finding plugins among the
mods. The other tests in that group stay Windows only.

mod_list.cpp replaced mods.json and the exclusion list with MoveFileExW.
That code is unchanged under _WIN32. Elsewhere it uses
std::filesystem::rename, which replaces the file in one step on POSIX,
and the error message comes from the error_code.

Ran on Linux (Debian trixie, linux-x64 preset with the plugin and
multiplayer tests on): dingosdk_plugin_tests, dingosdk_server and
dingosdk_server_plugin_tests build, and ctest passes plugins ("plugin
tests passed (15 skipped)", the gitignored contrib/plugins) and
server_plugins. The Windows build was not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-23 14:19:25 +02:00
Robin MoellerandClaude Opus 5.5 325af71214 Plugins: the role test ticks once before asking, so the session's players are there
The FakeGame test for player.role ran its console command straight after apply(), before the
host had taken the session's players (Host::follow_session runs from tick), so the server copy
indexed a nil player. It ticks each host once first, as plugins_server_copies_are_the_authority
does; the player's copy and the plugin without server are then refused for the reasons they test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-23 14:07:40 +02:00
Ryan CampbellandClaude Opus 5.5 9f8996bb69 Docs: a Troubleshooting section in the README, and a bug report template
The README answers antivirus false alarms, the Steam folder refusal, the
menu not opening and crashes, and the template asks for the version,
system, how the game was started and whether mods were on (#133).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:10:12 +01:00
Robin MoellerandClaude Opus 5.5 71e3460182 Plugins: server plugins know each player's role
A server plugin could not see the roles of a dedicated server (data/roles.json), so a plugin that
wanted only moderators to use some of its commands had to keep its own list of SteamIDs. In a
server copy with the server permission, a player table now has role: a read-only {name, rank,
staff}, the role the player has now (the one given them, or the server's default), read from the
server each time. staff is whether the role gives any of the server's commands. It is nil for a
player with no role, for the server itself and on a lobby's host. Each read counts as a server call.

dingosdk_server_plugin_tests reads a given role, the default role and a role given while the
player is on, and checks the table is read-only and a plugin without server is refused;
dingosdk_plugin_tests checks the API on a server copy, and that a player's copy and a plugin
without server cannot read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-23 10:43:34 +02:00
snubroot e8031fe8e3 Objects: mods name their Quick Drop objects and group them into families with reskate-objects.json
A mod's objects each got a tile of their own, titled with the raw key, so sizes or colours of one
object could not share a tile and X / Square had nothing to pick from. A reskate-objects.json in the
mod folder now gives them titles, families and categories, read at startup into a mod_objects map
kept apart from the game's own items. It only adds: taken ids and the game's items are left out.
2026-10-23 02:37:51 -04:00
AyeZeeBB e35e33d66d Plugins: board.parameters reads the board's shader values, and set_parameter holds its colours and numbers
Each part of the local board (deck, grip, trucks, wheels) has a material instance with a few
shader values of its own, found in the running game. reskate.board.parameters lists them by key
and type. set_parameter holds a colour or a number there each tick, published until it shows, and
the board's own value comes back when the plugin lets go or stops. Integers and switches pick how
the board is drawn, the store's foil looks among them, so plugins only read those. Only your own
screen changes.
2026-10-22 20:17:09 -05:00
AyeZeeBB 1671403500 Plugins: board.motion gives the board's spin, and where the board and its deck are in the world 2026-10-22 19:50:04 -05:00
AyeZeeBB 35824f4d1c Launcher: categories shown only or hidden in GET MODS and MY MODS, and the game's shader cache emptied when the mods change
- The category menu on GET MODS, and a new one on MY MODS, take each category as shown only, hidden or
  either (a click goes round them), with how many mods are in each; each page's choice is kept in the
  launcher's settings. On MY MODS a mod Thunderstore does not list, or lists in no category, is under
  "No category".
- Before a launch whose mods give the game other data than the last launch's (the mods with a layout,
  their files and load order, and the variants in play), the game's own shader cache
  (%LOCALAPPDATA%\ReSkate\Game\Skate\cache) is emptied: shaders kept from earlier mods were found to
  break the picture after many changes. The shader_cache folder the game ships with is left alone.
2026-10-22 19:05:51 -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 8ede361755 Merge pull request #341 from snubroot/fix/item-grid-4096-rows
Customization: item tabs over 1,024 items load instead of showing blank warning triangles
2026-10-22 17:11:08 -05:00
zeex64 e2c188cb75 Merge pull request #342 from moelrobi/plugins/server-parks
Plugins: what a dedicated server's plugins need to run a game mode
2026-10-22 17:02:17 -05:00
snubroot 8e79fd62f5 Customization: item tabs over 1,024 items load instead of showing blank warning triangles
The item browser reads grids up to 4,096 rows, but the native list writer it
publishes them through refused more than 1,024, so a bigger tab failed with
"Native menu list is invalid." every pass. The writer now takes up to 4,096.
2026-10-22 15:02:01 -04:00
Robin MoellerandClaude Opus 5.5 5b10469bad Plugins: server plugins spawn and remove the server's park mods
A dedicated server's park mods could only be spawned from its console, an admin's chat or a vote,
so a server plugin could not bring out the park its game is played in. A new permission,
server.parks, gives a server copy reskate.server.parks(), which lists the park mods set up with
whether each fits the map running now and is spawned (its objects and minutes left), and
reskate.server.park_spawn(name, minutes?) and park_remove(name), which return ok or false and why.

They go through park-mod add and remove, so the park limits, the map check and the chat lines stay
the server's. Their "[parks]" lines are logged "by plugin <id>". A name is a park mod's own (1 to 16
of a-z 0-9 - _) and minutes are 1 to 1440, or none for until removed. Reading the park files is not
part of this: a plugin brings what it needs to know about a park in its own package.

dingosdk_server_plugin_tests spawns and removes a park mod through a plugin and checks what it is
refused; dingosdk_plugin_tests checks the API on a server copy, and that a player's copy and a
plugin without server.parks cannot use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-22 20:43:01 +02:00
Robin MoellerandClaude Opus 5.5 fd5f95467f Plugins: server plugins know the map and hear it change
On a dedicated server reskate.level.current() was empty and level_loaded and level_unloading never
fired, so a server plugin could not tell which map ran or that it had changed. The server's
plugins are now told the map's level each tick, named as players' games name it (a destination's
level, after its "|"). When the server changes the map, they hear level_unloading for the old one
and level_loaded for the new one, as a game's plugins do; players load it after.

dingosdk_server_plugin_tests checks level.current() and both events across a map change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-22 20:38:22 +02:00
Robin MoellerandClaude Opus 5.5 45d4c64c75 Plugins: a dedicated server's plugins see the skaters
reskate.session.skaters() returned an empty list on a dedicated server: its game for plugins did
not say where anyone was, though the host holds every player's latest pose. A server copy now gets
each player in the world with a pose: their id, the pose's root position, its heading (read as a
client reads it) and how long ago it arrived. A server plugin can now follow players itself, for
a race's checkpoints or a game of tag, instead of trusting what each player's copy reports.

Host::skater_poses() lists them for the plugins. dingosdk_server_plugin_tests drives the host with
simulated players sending poses and checks what a server plugin sees, and that a plugin without
session.skaters is still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-22 20:30:07 +02:00
AyeZeeBB 45346b122c Plugins: prop hunt APIs, and plugin commands in the console lookup
- 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
2026-10-22 00:30:06 -05:00
AyeZeeBB c35e9a357b Docs: AGENTS.md for coding agents, and the README says the game build is pinned
AGENTS.md gives agents the build and test commands and the project's rules: one game build for
good, addresses only in Engine/Game/Build/20260929 with byte checks, hooks through Engine/Core/Hooks,
general SDK APIs with modes as plugins, sandboxed plugin permissions, nothing that unlocks paid
items, no EA code in the repository. CLAUDE.md imports it.

README's "Supporting a new game build" is now "The supported game build": ReSkate stays on 20260929.
STRUCTURE.md and README no longer point at contrib/plugins, which is kept out of the repository.
2026-10-21 23:19:40 -05:00
AyeZeeBB 721ed9e64d Plugins: a general launch API in place of grind_exit; grind exits is all Lua, and 701 is a grind
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).
2026-10-21 21:41:04 -05:00
AyeZeeBB 97793b79fc Build: the Linux server tests link the server plugins, every core compiles, no vcpkg hook
The release script's Linux step failed: dingosdk_server_park_tests and dingosdk_server_lock_tests
build server_host.cpp and server_commands.cpp, which call ServerPlugins since the server Lua API,
without server_plugins.cpp or dingosdk_plugins. Both have them now; the 18 Linux tests pass.
MSVC compiles with /MP instead of /MP4, so a project uses every core rather than four (the
release build's clean ReSkate.dll was the slow part).
A user-wide "vcpkg integrate install" ran its DLL copy after every link with PowerShell 7
("'pwsh.exe' is not recognized"); ReSkate uses no vcpkg packages, and the projects now leave it
out (VCPkgLocalAppDataDisabled).
v2.1.2
2026-10-21 20:59:42 -05:00
AyeZeeBB 77542e4db9 Throwdowns: a plugin refusing its card no longer blocks every later flag, and joins say what they did
A plugin that calls flag.leave() on `open` (Tag and Infected outside a session) sent the game an
exit from a setup that never opened. The game never took it, and every later flag's setup waited
behind it. A registration left before its setup was asked for now just ends, from flag.leave()
and from the waiting card's Leave alike (leave_unopened).
The join card says which flag it read and how it was branded, or which buttons it has when it has
no Enroll; its branding errors were swallowed. Joining another player's plugin flag says whether
its plugin heard join, or why the player was taken out of its queue. A navigation that waits on
the game's says so every 3 s, and a setup that does not open in 15 s says so.
2026-10-21 20:42:49 -05:00
AyeZeeBB a598ce602b Launcher: the start map is the game's first level, without San Vansterdam before it
The first level the game loads from the splash took San Vansterdam, and the startup commands'
"load" then loaded the start map over it. queue_menu_bam now takes the map from the front of
RESKATE_STARTUP_COMMANDS when it is installed and can load (its start point as the console's
"load" picks one), and the startup commands drop that "load" rather than run it again. A map that
cannot load then still leaves the game in San Vansterdam.
2026-10-21 20:42:49 -05:00
AyeZeeBB d1bdb0f8a6 Launcher: a start map in Settings, GAME, instead of always San Vansterdam
The launcher lists San Vansterdam, the game's other maps and the custom maps of the mods that
load (start_maps.h), keeps the choice with its settings and starts the game in it, as its -map
does: a "load" at the front of the startup commands. A map no longer listed when the world can
load it (a map mod switched off or left out of the merge) is dropped after 20 s, and the game stays
in San Vansterdam. A launcher kept open no longer adds a launch's map to the last one's commands.
Tests: Launcher/Test/start_maps_tests.cpp.
2026-10-21 20:12:18 -05:00
AyeZeeBB ecacdd4242 Plugins: a change to a plugin's files waits for the game to have the focus
A plugin edited in another window reloaded at every save while the game was in the background.
A change found then is held, and loads once when the game window has the focus again (the log
says it waits); the first set of plugins still loads at once, focused or not.
2026-10-21 20:12:18 -05:00
AyeZeeBB 48d1e245db Outfit layers: an item worn on top keeps its textures, and the log says what the layers do
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.
2026-10-21 19:49:35 -05:00
AyeZeeBB 89a97caed7 Plugins: an item grid's own list and marks, items worn on top, keys kept from the game, the menu's category icons
General APIs from pull request 269 (the player's own item tabs and outfit layers), whose tabs,
panels and prompts are now a plugin's (contrib/plugins/item-tabs, kept on this machine):
- reskate.menu.grid_show(keys): a plugin's list in the item grid that has the focus, drawn from
  every grid of the screen; reskate.menu.grid_mark(keys): the game's check on tiles. menu.grid()
  also says whether a list shows, whether it is empty, and where the grid's own prompts end.
- reskate.outfit.on_top(items) and on_top_state(): items worn on top of the outfit in the slots it
  leaves empty, the item given the slot's category, a slot tried and left if the game reports a
  failed load (ReSkate.layer_slots.txt remembers which load where); paid store items refused.
  Part of the shared outfit service, with reskate.outfit.set.
- reskate.input.game_keys(keys): presses the game does not see, by any way it reads the keyboard;
  "Delete" is a key name.
- d:game_image draws the customization menu's 17 category icons ("cas_tops", ...).
dingosdk_plugin_tests skips the test of a contrib plugin that is not on this machine.
2026-10-21 19:18:01 -05:00
AyeZeeBB 913b4ce422 Merge remote-tracking branch 'origin/main' 2026-10-21 19:01:43 -05:00