Resolve private effects only from the level-wide Game compartment and validate
their resident declarations with the same parser used by the mod merge.
Release surviving native services during world changes, check decal realm
ownership before cleanup, defer ordinary off-thread releases, and prune expired
VM handles so they return their quota. Size the temporary decal allowance from
the live count while preserving external settings changes.
Gate contact geometry behind events.physics.geometry, document the manifest
migration, and test Blood impacts through the plugin host using its own manifest.
Add regression coverage for residency, lifetime, quota and draw-budget handling.
Validation: Release builds of the runtime and plugin/effect test targets succeed.
The plugins, effect_geometry and effect_lifetime tests pass. Existing MSBuild
asset-directory dependency warnings remain; native visuals need an in-game check.
Add permission-gated effect handles, native surface attachments, and world lifetime cleanup. Extend physics_step with optional contact geometry and body poses. Document the generic API and validate it with self-contained plugin and geometry fixtures.
What the live apply already does without one, how the engine loads and holds bundles (the resource
manager's load operations, the level's compartments), why cosmetics need the reload (the root
level's cas_main_sharedbundle), and a plan for new items: a child BundleRefTable, a delta bundle
and the cosmetics manager. With experiments to run in game and the open questions. On hold for now.
The plugin folders (the ports, the test plugins, Fit Check, Animator Studio, SmokeyDawg58's throwdown kit and the tools) are no longer tracked and contrib/plugins is ignored as a whole. The files stay where they are; dingosdk_plugin_tests still runs them when they are present.
Pull request 326 split into general plugin APIs and contrib/plugins/fit-check:
- reskate.capture (start, stop, discard, state, compare, kept, save): the screen
recorded before the overlay draws, cut out by the game's depth or by colour,
saved as GIF, APNG, PNG or WebM with an optional card; the "capture" permission.
- reskate.room.turn: a stand-in pad's stick held over to turn the menu's preview.
- reskate.menu.game and reskate.menu.grid: what the game's menu and its item grid show.
- reskate.outfit.worn, board and title: what the skater wears, by name.
- reskate.files.reveal: a data folder opened in Explorer.
The plugin finds where the turn comes round, eases the stick off, picks evenly
spaced frames and saves them with the board's parts or the outfit on the card.
- reskate.animation.mirror makes a clip for the other stance (Left and Right trade across the board's
length, each joint from its rest; the board mirrored as itself), and info gives a clip's stance. The
game's trick clips stand goofy: the studio mirrors one for a regular skater, which played half a
turn round in game.
- The studio no longer holds the skater with noclip: he left his board and the flight pose showed
through. His board is stopped once instead; while drawn in the room No Bail guards him, and the
game's own placement goes back into the pose before its physics.
- The whole board is held rigid while a clip moves it, deck mount included.
- The room closes when the game's menu leaves the Skaters page, and the studio closes with it.
- The studio's first camera angle is the PR's, taken once the room's joints are read.
- A log line every 2 s while a clip plays (Clip pose: updates, renders, scale, heights, the joints
that change most), kept while the room's pose is still checked in game.
- The studio waits until a bail is over (its state, or 4 s after the bail event): held still mid-bail,
the game took the hold away and the skater went on bailing behind the studio.
- The character room's walk back gives up after 8 s: with no skater ready (a respawn) it could not
step, and the studio stayed on its loading screen.
- reskate.animation.game_clips lists the game's animations and "game:<name>" plays one: the
skater's Eclipse clips decoded from its data (Extension/AnimationStudio/game_clips, ported from
ReSkate Studio's animation export), matching make_trick_library.py's clips.
- reskate.animation.play's floor stands a clip on the floor where the skater is, its board's lowest
point on it, as the pull request's studio did; the game's trick clips no longer sink.
- Animator Studio lists the game's tricks from its data; the Python tools are gone.
- reskate.room.open, leave and state, with the room permission: ReSkate walks the game's menu to the Skaters page (its navigation events and RB on an internal scripted pad, as PR 297 and PR 230 did), draws the local skater on the room's floor and hides the room's own skater and board; other players are not sent the room. Extension/World/character_room, the room placement and hiding in skater_animation, and post_native_ui_event made general.
- reskate.input.text: what is typed while the plugin's screen is open; while it types, Esc is the plugin's.
- Animator Studio opens in its room (the Studio room setting, as the PR's), under its loading screen, or where the skater is when the walk fails; its card's name, trick and numbers are typed into in place.
- The studio plugin draws PR 297's screen itself: skate.'s Trick List menu and card, the joint timeline with key rows, dragging joints on the skater, keyboard and pad controls with their prompts, the unsaved-changes card, the recording strip and the loading screen. It lists a trick library made with tools/make_trick_library.py (the PR's, here beside it), and checks itself with studio test.
- Drawing: d:rough_rect, d:halftone, d:scribble, d:clip, d:measure, a text wrap width, the title_large font, text up to 400, and the loading screen's kickflipping board as d:game_image("loading_board") with a frame.
- reskate.ui.screen with panel = false: input taken, nothing drawn, the plugin draws it.
- reskate.skater.joint gives each joint's parent; reskate.skater.pose takes an offset.
- reskate.animation: play's at and paused, seek, pause and time; info, sample, joints, trim, edit (keys baked onto a clip) and save; clips named "data:<path>" from the plugin's data folder, "clip:<n>" and "edit:<name>".
- reskate.ui.screen's dock and width: a panel down one side, the game showing beside it.
- reskate.input.cursor: the mouse over the game while the plugin's screen is open.
- contrib/plugins/animator-studio: the Animator Studio of PR 297 (22sh, Fourth Studio, Norms) as a plugin: record a trick, key joints on a timeline, save it, and the trick plays it in game.
Two general trick APIs on the hooks TheRealEJR found for his Rock to Fakie mod (pull request 285):
- tricks.label(from, to) ("hud"): the trick ticker shows `to` where the game's label is exactly
`from`, on the ticker's own call route and in its own buffer only, when it has room; 16 labels a
plugin, given back when it stops.
- tricks.record_as(from, to, within_ms) ("skater.control"): the next trick the recorder gets whose
id is one of `from`, within the window, is recorded as `to`, through a changed copy of the packet.
One ask a plugin, used once; refused while a throwdown runs, as trick replacements are.
The hooks pass everything on unchanged while no plugin asks.
contrib/plugins/rock-to-fakie is his feature in Lua: while the physics state holds a stall (400 or
401) the ticker shows Rock to Fakie for a Board Stall or a Lip Stall, and B pressed in it asks that
the stall be recorded as the game's fakie_rock (187). Off until `rocktofakie on`. Checked in the
plugin tests, with the new functions' permissions.
Made from GoalTrains' pull request 254 without its C++: a page in the PLUGINS section with the
Session-style and XL-style presets, the five custom slots the companion's own menu shares, stance,
stick layout, steering angle, stock ramp controls, the startup choice and every other setting, with
the companion's keys and fallbacks. It edits session_controls.ini in the plugin's own data folder
(reskate.files), keeping every line it does not change, and never creates the file; the README says
what the companion needs to change to read it there. `dualstick` in the console, and its own checks
(`dualstick test`) in the plugin tests.
ReSkate.dll exports ReSkateOverlayOwnsInputV1, from the same pull request: 1 while ReSkate's menu, a
free camera on the pad or a prompt has the input, for any native tool that runs beside the game.
reskate.files kept each plugin's folder in Mods/.reskate/data/<plugin>/, inside the merge's own
output, which every full merge deletes: saved Deathrace routes went whenever a mod changed. It is
Mods/.reskate_plugin_data/<plugin>/ now (mod_list.h plugin_data_folder), and what is still in the
old folder moves across the first time the plugin looks for its folder. Deathrace's routes keep
SmokeyDawg58's <map>/<name>.json layout, so routes from his fork copy straight into routes/.
- match.teleports lets through only the publishing plugin's own teleports, and only while its
match runs (its HUD is active, or the native flag is one of its modes); it was merged across
every plugin and checked no match.
- A mode's timer of 0 or past an hour no longer makes its card do nothing: the native flag takes
it as 1 to 3600 s. flag.begin's seconds are checked as 1 to 3600, as the flag takes them.
- skater.push and hold(true) return false with a reason without the stock physics, where they
were dropped unseen.
- telemetry's slam and struck are news since each plugin's own last read; reading them cleared
them for every plugin.
- A banner or callout of a new match shows whatever serial it starts from, and a document without
the slot no longer resets its serial.
- SmokeyDawg58's placement no longer divides input.wheel's whole notches by WHEEL_DELTA again.
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>
contrib/throwdown-kit.md is the reference for making a throwdown mode in Lua: registering modes,
the documents (setup page, waiting card, HUD, match panel, flags, match), the throwdown_ui events,
the native flag, and the services a mode uses. Board Break's doc says how the deck is chosen now
(the pack's decks as variants, reskate.package.choose, reskate.board.fit); Road Rash's that it ships
inside its item pack. The plugin list and the README name the throwdown plugins.
A mod's reskate-plugin.json can declare "variants" {folder, order, displaces}: each <folder>/<name>/
is a variant, the one chosen (else the first of its order) loads, and every other mod whose layout
lists a displaced bundle is set aside for the run. Choices are kept in
%LOCALAPPDATA%\ReSkate\variants.json. Plugins read and choose their own mod's variants with
reskate.package.variants and reskate.package.choose (permission "variants"), and fit every board
for a deck that is not the game's own with reskate.board.fit {plates, axle}.
The board-break plugin, which ships inside the Board Break deck pack, declares the pack's decks as
variants, picks the deck on its own page and with `boardbreak deck`, reads each deck.ini and fits
the boards. Gone from the SDK: breakable_deck, the deck catalog, the MODS page's deck card, the
boarddeck command and the Board_Break_<Name> folder naming.
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.
His mod ships its assets and this plugin together. Each mode has its name, description,
logo, flag, players, timer and setup rows as his fork had them; lobby events are said in
chat. Their gameplay is ported mode by mode; until then Start says the mode is not
playable yet. The mod's assets stay out of the repository.
reskate.throwdowns.mode{...} adds a mode to the game's own Throwdowns page: a card on
the picker, the real Spot Battle setup page relabelled with the mode's name, logo,
description, players, timer and rows (sliders, choices, buttons), and the native start
flag. The plugin hears throwdown_mode events (opened, setting, button, placed with the
flag's position and heading, start, leave, cancelled) and runs the mode itself;
release_flag takes the flag down. The flag's queue never starts a Spot Battle: its
waiting card's Start goes to the plugin, MaxPlayers is held at 10 and it is never
relayed as a drop. After SmokeyDawg58's one_up_menu and one_up_placement
(reskate-custom-mods), generalised over a mode registry.
The page is his 4-column logo grid whether or not a plugin has a mode. Its widgets and
grid records, and the game assets it uses that only some levels load, ship in
ReSkate.dll as the throwdown_menu built-in pack, taken from his asset mod's FBMOD
(dingosdk_ebx_pick trims the asset list in place). A mode's logo is a texture path in
its own mod, so no mod edits the shared list. The FPV drone is no longer a built-in pack.
Native menu data: text initialization, change-only writes, retained array publication,
owned-model rollback. Tests: registry and setup helpers, the plugin API, a test plugin
with two modes.
Each plugin gets its own page instead of sharing the ADDONS page's PLUGINS
tab: a row per plugin in the window's side rail under PLUGINS, a tab per plugin
full screen. reskate.menu.page(title, build) makes it; the plugin's panels show
as cards under it, and a plugin with panels only gets a page named after it.
A page is built ten times a second, only while the menu shows it.
The menu's own widgets for pages: ui:card(title, subtitle), ui:tabs, ui:note,
ui:warn, ui:info, help text on toggles and dropdowns, a slider's format
("%d%%") and a card's main button. Full-screen screens draw them too.
Road Rash, Board Break, the FPV drone (a tab each for fly, flight, rates,
display and controller), Hall of Meat and grind exits have pages, worded as
their built-in pages were. MUSIC is ADDONS' only addon now.
Hall of Meat (contrib/plugins/hall-of-meat) and grind exit triggers
(contrib/plugins/grind-exits) are Lua plugins: the bail model, its Meat card,
each map's best, the trigger rules, settings, console commands and menu panels
are theirs, with their own checks that dingosdk_plugin_tests runs.
What has to run in the game stays in core as plugin services:
- every physics step of the skater (skater_physics, the physics_step event,
permission events.physics);
- the skater's x-ray (xray, xray_render, xray_skeleton; reskate.skater.xray and
the draw handler's d:skeleton);
- skate.'s bottom left HUD corner (hud_corner; reskate.hud.corner);
- the game's slow motion, never in a session (game_speed; reskate.time.slow and
speed, permission game.speed);
- the side of the next pop out of a grind and pad controls kept from the game
(grind_exit; reskate.skater.grind_exit, the grind_exit event, reskate.input.hide);
- skate.'s own pictures by name (game_images; d:game_image), d:gradient and a
font for drawn text.
Removed: Extension/HallOfMeat, grind_exit_control and its commands, the HALL OF
MEAT and GRIND EXITS add-on pages, home tile, hub and native menu rows, their
menu models, and the hallofmeat and grindexit built-in commands.
The 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.
- 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.
- 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.
- With events.tricks a plugin hears the local skater's tricks: trick_started (where), trick_landed
(points, multiplier, where it started and landed, how long it took) and line_broken (a slam or
a wipeout); reskate.score.current() is the score HUD's line, multiplier and whether a trick is
going on. Without the permission the events cannot be registered.
- The score HUD is read into the trick watch (Extension/Skater/score_hud.h, trick_watch.h) on the
client tick only while a running plugin has events.tricks; a new level starts a new line.
plugins::start takes the image base for it.
- reskate-test has a tricks module (skipped until a trick lands) and ptest trick, which waits 30 s
for one and prints what its events said; reskate-hostile has trick-loop.
Not yet checked against real tricks in game.
- skater.read: reskate.skater.get() (position, heading, speed, vertical speed, airborne, air
time) and position, heading, speed and on_ground on their own; nil without a skater. From the
trainer's telemetry. The heading is radians, -pi to pi: 0 faces +Z, pi/2 faces +X, and a higher
heading is turned to the skater's left (checked in game).
- skater.teleport: reskate.skater.teleport(at, heading?) through teleport_local_skater_heading;
false and why when the host has turned teleporting off (as the console's tp), there is no
skater, or it is asked more than twice a second. Positions must be finite and within 100 km.
- world.raycast: reskate.world.raycast(from, to) is where the line first meets the world's
collision (local_surface_hit), or nil; 64 a tick, 1000 m at most.
- The host passes the game to plugins through Environment, so the tests fake a skater and flat
ground. reskate-test has a skater module (skipped without a skater; the harness counts skips)
and ptest tp; reskate-hostile has teleport-spam and raycast-flood.
Checked in game: teleporting, the two-a-second cap and the raycast cap. ptest skater's heading
check failed there (0 to 2 pi from the trainer) and is fixed, not yet rerun in game.
The console answered every command missing from ReSkate's own list itself, as unknown, so a
plugin's command (ptest) never reached the plugins on the game thread. Now only ReSkate's local
commands run there; anything else is queued, and the game thread tries the plugins first and
answers unknown as before when none has it. Checked in game: ptest all, 45 passed, 0 failed.
- contrib/plugins/reskate-test calls every plugin API and says what worked: ptest all, ptest
<module>, ptest list. Its 45 checks cover the base set, timers, the level, package files, JSON,
require, the console, storage and the sandbox. It is the example to copy for a plugin.
- contrib/plugins/reskate-hostile attacks the sandbox, one attack per command (hostile list):
loops, memory and buffer bombs, caught errors, error and log floods, timers, recursion, an
unload that never ends, and 17 ways out. Each must end stopped or refused.
- dingosdk_plugin_tests runs both from the repository: every reskate-test check passes, and every
hostile attack is stopped or refused with nothing escaping.
- Taken from Domination (PR 279) for the plugin API to use: the trick watch (when a trick starts,
lands and what it scored, or a slam breaks it; Domination's spots left out) in
Extension/Skater/trick_watch.h with its tests, the score HUD reader in score_hud.h, and
teleport_local_skater_heading, whose hunks are the PR's own. Neither is wired to anything yet.
A rebuild builds one segment per key and each needed every key's slot, so a long replay built
them once per segment: the game's rebuild is hooked too and the slots built for its first segment
are kept for the rest of it (per Behaviour and Subject). The segment player is no longer hooked
(it only counted plays for keysmooth status, every frame). The smoothness is a slider beside the
switch in the overlay (sent when let go, as each change rebuilds the open replay's camera) and
Keyframe smoothness (100, 75, 50, 25%) in the pause menu and Mod Options > Visuals. The startup
line logs on the runtime channel.
The game plays the replay camera's framing keys as cubic Hermite segments whose speed changes at
every key (the acceleration jumps there, and it brakes where a channel slows or turns back), so a
free-camera shot surges and settles around each key. keysmooth on rewrites each segment as the game
builds it, so that the game's own player plays the natural cubic spline through every key: speed
and acceleration carry on through the keys, and every key is still met exactly, at its time.
- keysmooth on|off (off by default), keysmooth amount 0..1 (1 by default; 0 is the game's curve)
and keysmooth status; kept in %LOCALAPPDATA%\ReSkate\KeySmooth.settings. Only the free camera
(Behaviour 1) is smoothed; the other camera modes keep the game's curve.
- A switch in Mod Options > Visuals and in the overlay's Skater > Freecam.
- The curve apart from the game, with tests (DINGOSDK_BUILD_REPLAY_KEYFRAMES_TESTS); the hooked
functions are fingerprinted, and a build that differs gets no hook.
- contrib/keyframe-smoothing.md: what it does, how, and what was seen in game.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
The game's truck model has its hangers on the trucks' bones and its
baseplates on the deck's bone. A broken board hides the real deck by moving
that bone away, so its trucks lost their baseplates.
The decks inside a deck pack bring a truck model with the baseplates on the
trucks' effectors, bones under the trucks' own that no model uses. The render
pose hook writes each effector from its truck's bone, so that the baseplate
sits on the deck of a whole board however the truck steers, and goes with the
truck when a piece carries it off. With the game's own truck model nothing is
on those bones and nothing changes.
The launcher installs one mod folder per package, and the game takes one
mod's deck at a time, so a package could only bring one deck. A mod folder
may now hold a deck in each decks/<name>/, laid out like a mod folder. When
the mods are put together each of them becomes a mod of its own for the run,
listed as <folder>#<name>: it is chosen, set aside and switched like a deck in
a folder of its own. mods.json and the mod list only know the pack, which
counts as loaded when one of its decks is.
A pack's own layout is not one of its decks, whatever the folder is named,
and is set aside like any other deck model. It is what an older ReSkate
loads from the pack: 2.0.6 knows a deck by its folder's name only, so a pack
in a folder named Board_Break_<Name> with a breakable deck as its own layout
breaks boards there with that one deck.
A slam can break the local skater's board at the nose, in the middle or at
the tail. Half the time the grip tape holds the pieces together and the board
folds or an end hangs; otherwise a piece comes off, flies and lies where it
lands. The board is whole again in hand, on riding and on a respawn.
The pieces are four stretches of the deck in a content mod's copy of the
game's one deck model, each on a spare bone of the board's rig. The boards'
render pose hook only moves bones, because the replay records where a bone is
and not its size: a broken board's deck goes away and its stretches go to
where the pieces lie, and a whole board's stretches go away.
The deck the player chose loads first and other mods that bring a deck model
are set aside for the run, without touching mods.json. Another deck is chosen
while the game runs, through a live apply of the mods.
contrib/board-break.md says how it works and how a deck pack is made.
- The Release rule's regex held a "|", which Pterodactyl reads as a rule
separator, so creating a server failed with a 500. Same check, no "|".
- The panel wrote port, query_port and steam_token at the top level, where
the sectioned config ignores them. They now go to server.*.
- Add a Listed variable. The panel's JSON parser writes "false" as a
string, so the startup command sets server.listed with sed instead.
- The first install writes its settings into the server section.