combine() recorded every source's copy of every EBX and resource asset, so that conflicting edits could be combined later. Map mods carry near-complete copies of the game's root-level bundles, so most of those copies are the game's own bytes. A mod's copy equal to the game's first copy of the asset, the copy edits are combined onto, is no longer recorded; the game's copy, recorded first, stands for it. A contribution points at its source's folder instead of copying the path. The merge report counts the copies left out.
The tests read the merged patch through CasStore and list_bundle, report a missing archive as a failure, and cover a mod archive numbered like a placeholder.
The music mod's song names its audio chunk; each map that takes the song carries the chunk too, and every chunk entry in the patch points at bytes inside its archive.
A carried asset was written again for every superbundle that carried it: 1.4 GB of 2.2 GB with 96 mods, which left no room for a live apply. Each one is now written once per install chunk, when its first superbundle settles.
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.
The renderer gives each frame 131,072 shader-visible descriptors past a persistent 120,000, in
heaps of 333,333. Dense custom maps (the Los Santos City sublevels, San Andreas) draw more than
that in a frame, and the renderer wrote past the end of the heap: shaders read garbage
descriptors, which faulted or hung the GPU (page faults at nonsense addresses, in draws, texture
fetches and ray tracing alike). ReSkate now patches the device setup at boot so each heap is
D3D12's 1,000,000 and a frame may use 524,288; heaps are 644,288 descriptors. LS-B2, LS-B3 and
San Andreas (210,000 a frame at its peak) now hold.
Also in this change:
- Memory: perf memory owners/heap/gpu/report, a ledger of who commits the game's RAM; the
render-resource pool no longer allocates its 115 MB twice; the EadpErrorsData arena is
capped at 32 MiB (EA's error reporting is blocked anyway).
- Crash reports: tagged with the map, destination, graphics card and driver version (protocol
v3), so Sentry can group GPU hangs by map and card.
- GPU diagnostics (--gpu-diagnostics): a fuller DRED report, the device-removal and video
memory watches, a large-draw watch, a record of which game code made and freed each GPU heap
and resource, checks of every GPU address bound, and a meter of each descriptor heap's use.
- World pools: peak use of entity pages, render instances and spatial blocks in the log.
- dingosdk_mod_mesh_check: checks every MeshSet a mod ships against its geometry; the MeshSet
reader now knows static meshes' layout.
A dedicated server that rotates its maps can now ask players where to
go next instead of taking the pool's next map.
Server
- maps.rotation_vote: when the timer runs out, a vote between three
maps drawn at random from the pool, "Random map" and "Stay here".
The most votes wins, a tie is drawn, and with no votes it is the
pool's next map. It ends early once everyone has voted.
- maps.rotation_vote_seconds (10-120, default 30) and
maps.rotation_extend_minutes (0-240, default 15; 0 leaves "Stay
here" out).
- maps.rotation_announce: a card when the map has 10, 5 and 1 minutes
left.
- The vote's maps are drawn a minute ahead and sent to games
(PacketKind::next_maps), so their pictures are ready when it opens.
- /vote extend: a built-in vote that gives the map in play
rotation_extend_minutes more. On by default (votes.extend_map), and
only runs while the maps are on a timer. "extend" is now a reserved
vote name.
- Console: rotation vote on|off, rotation announce on|off,
rotation stay <minutes>|off, rotation extend [minutes],
votes extend ..., vote extend.
Game
- The vote opens as a popup in the middle of the screen: a tile per
answer with its picture, name, share of the votes and count. It
holds input as the map download card does, and works with a
controller (D-pad, A, B), the keyboard and the mouse.
- Pictures: a mod map's icon from its folder, or from Thunderstore
when it is not installed; the game's own maps show their loading
screen picture, cut to its middle square. "Random map", "Stay here"
and a map without a picture are drawn as shapes.
- Older servers and polls are unchanged: the vote is a poll, so it can
still be answered with /1, /2... in chat.
Protocol 50: games and servers both need this build.
Votes: a vote is decided by the votes cast, not by everyone on the server. It
passes when yes is its percentage of them, at the end of its time, or sooner
once the players still to vote could not change it. A kick still needs two
yes. The card's bar is yes in green from the left and no in red from the
right, with a mark where yes has to reach. No protocol change.
Map versions: a map installed at another version than the host's is no longer
loaded as it is. The download card offers the host's version (MAP VERSION,
UPDATE), which replaces the installed one and is applied while the game runs.
For that the live merge keeps each mod's fingerprint with where its archives
were placed, and places a mod again when its files have changed since: before,
a mod replaced while the game ran was read where the old one lay. When the
host's version is gone from Thunderstore, or the old one's files are in use,
the player is told and leaves the session.
Dedicated server: "maps reload" reads the Mods folder again, so a map copied
in while the server runs can be switched to at once. The server window reads a
copy of the map list.
Map names: a custom map's level is dingolevel_reskate_<map>, and "reskate" was
left in its name ("/map reskate badlands"). The map's own name is shown and
typed now; the old spelling still finds it.
as a level unloads, the game takes each mesh variation out of its registry by the
entry's mesh and reads it unchecked, and this map's meshes unload before
their databases. A guard on that one function has the read of a mesh that is
gone go on with the next entry, and logs how many were passed over.
Memory
- The entity and pending-update pages are four times the game's own size
(524,288 slots), down from eight times. At eight, the game kept a
3.5 GB block for them that was fully in RAM on custom maps; the largest
map load seen needs about 150,000 slots. The render-instance pool and
spatial buckets stay at eight times.
- The log has a "Memory:" line when the game's memory, the players in the
session or the skaters built change: committed, in RAM, players, skaters
built, player distance and map.
- Console: `perf memory` lists the game's memory by block size.
`perf memory mark` remembers it, and the next `perf memory` says what
was added since. `perf memory trace` also records who asked for each
block until `perf memory stop`. `perf memory big` lists the largest
blocks with how much of each is in RAM and a sample of what is in them.
RESKATE_MEMORY_TRACE=<megabytes|all> starts the trace with the game.
Replay crash guard
- The game's replay recorder crashes when its stream has no room for the
next chunk: it writes to the chunk it was not given (Skate.exe+0x48efa34,
crash reports of 2026-10-10). The function now runs under a handler
that, for that one fault alone, returns as if no chunk was ended, and
says so in the log. Any other fault still goes to the crash reporter.
The skip path has not been triggered in testing.
Launcher
- `-map <name>` (also --map, -map=<name>) loads that map as soon as the
game is up, named as the console's `load` names one. With --no-gui and
no other options the launch still uses the saved launcher settings.
Pause menu
- The Multiplayer and Mod Options pages step aside while the game's side
menu, or a screen opened from it such as Settings, is over them. They
drew over both before.
- A button still held when a page comes back is not a press on it: the
Back that closed the side menu no longer dismissed the page as well.
Multiplayer cosmetics
- A player whose mods add or remove cosmetic slots (stickers, say) no
longer has the whole outfit refused. Their items go to this game's slots
by slot: a slot they lack stays empty, an item in a slot this game
lacks is left out, and the log says how many of each.
Map downloader
- A host or dedicated server now tells joiners which Thunderstore package
its map comes from (the Mods folder name plus manifest.json).
- A player without the map gets a card: icon, name, author, version and
description, with DOWNLOAD and CANCEL. It works with mouse, keyboard and
controller.
- Only packages listed as Maps in the ReSkate community are offered, and
never deprecated, rejected or NSFW ones.
- On yes the package is downloaded, installed into Mods and applied as a
live mod, then the session loads the map. A small card on the right
shows the progress, so the player can keep skating.
- The player stays connected while it downloads: the host gives a player
who says they are fetching the map up to 45 minutes instead of the
usual load time.
- When the server changes map with the player in it, the card says so.
- Protocol version 47: map package on map_offer and world_state, and a
"fetching the map" flag on map_request and world_ready.
Joining
- The pause-menu server browser shows a CONNECTING card over the page
while a join is under way, and a COULDN'T CONNECT card with the reason
when it does not get in.
- The session and voice tabs no longer show before the player is in.
- Leaving without the map (declined or cancelled download) no longer
counts towards the failed-join limit, on servers and player hosts.
Input
- A card that asks something holds the game's input until the buttons
are released.
- Steam's overlay hooks XInput in front of ReSkate and answers the game
itself, so a controller reached the game behind menus. ReSkate now
hooks that hook as well.
Mod merging
- Mods are read on several threads, at launch and for live merges. A live
merge runs below the game's priority.
- The paid-cosmetics check reads the game's files and the mods on several
threads.
- A live merge reuses the paid-cosmetics verdicts it already has and
reports its progress and timings.
Launcher
- NSFW mods and their categories are not shown in the mod browser.
Player list. A player on the developer, staff or homie list sees each
player's Steam ID in the overlay's PLAYERS list for the session they are
in, after "Host" / "Guest" and the distance, on their own row too. A click
copies it. The lists are the backend's, the same ones the tags come from;
every client in a session already knew the IDs, so this changes who is
shown them and nothing else.
Custom scripts. Nothing in scripts/Custom is read or run any more, whatever
the launcher's "Loose files" setting says: nothing used them, and a mod was
using the folder to load a DLL into the game (package.loadlib from its
init.lua). The loader is kept behind one switch (custom_scripts_enabled in
custom_script_loader.cpp) for the day they come back.
For that day, the loader now also takes Lua's ways of running native code
away before the first custom script runs: package.loadlib, require through
the two C searchers and package.cpath, os.execute and io.popen. If that
does not hold, no custom script runs.
The overlay's Mods page no longer has the INSTALLED / SCRIPTS tabs; it is
the installed mods. Editing the game's own Lua and config through loose
files is unchanged.
As in skate. 3: when the local skater bails, the bones it hurt show over
the world through the body, yellow where a hit bruised one and red where
one broke it, with the bail's Meat card in the bottom left corner in the
place of skate.'s own HUD there: its time, hits (head hits double,
vehicles half again), broken bones, road rash, airtime, fall, top speed
and rotations, each scoring, and the map's best. A bone breaking pulses
the screen's edges red and, in single player, slows the game down a
moment. Off until switched on, as in skate.: in skate.'s menu (Custom
Stuff > Player) or with `hallofmeat 0|1`, saved with the profile.
Everything it draws is skate.'s own, read from the installed game: its
skeleton mesh (the Dem Bones costume) posed with the renderer's own
skinning matrices, its scoring icons, the THRASHER wordmark and its UI
brush strokes. Nothing is shipped.
How it works: No Bail already hooks the skeleton response, which runs
once a physics step right after the body contacts; it now hands that step
on, with the local skater it follows, but only while Hall of Meat is on.
Each step reads what every ragdoll body hit and how hard, and whether the
skater is a ragdoll; the bail runs on the game's own clock so slow motion
measures what the body did. The feature's own hooks, on the renderer's
skinned draw packets and main view and on the UI model's write, are
prepared at startup and switched on only with the feature, and off again
once the HUD corner and the game's speed are given back: switched off, the
game never calls them and the physics step pays nothing. The skeleton mesh
is read the first time it is on; only the images are read at startup,
since the overlay builds its atlas once. The renderer's picture of the
skater is read only while a bail shows. The menu's switch reads the
overlay model like the others. Every address is in the build table with a
fingerprint checked before use. Extension/HallOfMeat/README.md describes
the parts; its tests build with DINGOSDK_BUILD_HALL_OF_MEAT_TESTS.
The slow motion changes SimulationTime's rate, frame cap and time scale as
a session override and steps the local skater along, since skate. sets a
skater's step only when it builds its core. It runs the same at every
break, but never faster than the game ran: with the trainer's game speed
below 1 it only takes the part that is slower still. Afterwards it writes
back what it found, each setting only while it still holds what the slow
motion wrote, so the trainer's speed comes back as it was; a setting
changed from elsewhere meanwhile is left as it is and ends that slow
motion. named_setting_value reads a setting's value for it.
Tuned in play: a bruise from 7 m/s and a break from 12 m/s along the
contact normal (raised twice: a hard fall on flat ground broke bones), a
break worth ten of the lightest hits (a heavy bail's hits and breaks
outweighed the rest of the card) and a fall half of skate. 3's rate.
Fix: the overlay now draws in the back buffer's pixels, not the window's,
so it lines up on displays Windows scales (1920x1080 borderless on 4K).
The skater's render hooks, its skeleton and its body maps were first
found by gBGYo's ReSkate fork (github.com/gBGYo/ReSkate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Mod merge: rebuild a merged bundle's chunk metadata the way the game
writes it (one record per chunk, in chunk GUID order), so costumes
from different mods built on the same game costume keep their
textures and meshes
- Merge audit checks that layout, and there is a new unit test for it
- Server names: only letters, numbers, spaces and - _ [ ] ( ),
1 to 64 characters; the server browser hides servers with other names
- New Centrix tag from the backend's "centrix" list: blue and white
name, badge and item animation, ranked after Dev and before Creator
Merge: carry added assets with a changed list, combine in-place values, list repeats once
Three merge bugs found by auditing 104 installed mods:
- A changed list (the music playlist) reached every bundle copy that holds
it, but the songs a mod added beside it only reached copies of the bundle
the mod added them to. The game keeps the playlist in eight bundles, so
the other seven named songs they did not hold. Added assets now follow
the change into whichever bundle copy takes it.
- An entry naming an asset that lost its name to another mod's asset is
now dropped from the list, both in the merged bundle and in the list
other mods' copies are given.
- Two mods changing values in place in one asset (lighting) kept one mod's
copy whole and silently dropped the other's. Plain values are now
combined, the higher-priority mod winning where both changed one. An
edit that also moved a reference or resized a list is taken whole or not
at all, and the merge notes whose changes are missing.
- A reference two mods both append to a list is listed once.
- Mods that add copies of store items under new names are no longer
loaded. The launcher, mod manager and game say why.
- A chat line passed on by the host only shows a Dev/Creator/Homie
badge when the player's own copy of it arrives too, so a host
can't put one on a line that player never wrote.
When several mods change the same game asset (the master music playlist,
for one), only the first mod's edit was read for the imports it names, so
only that mod's added songs, waves and chunks were carried into the other
mods' copies of the bundle (a map's level superbundle). Every later mod's
additions went missing on those levels while the game's own levels kept
them, which showed up as songs vanishing from the playlist on custom maps.
Read the imports of every changed asset, and merge a later mod's edit into
the stored change so the combined document's imports -- and the chunks
they lead to -- are followed too. Add a regression test with two music
mods whose songs must both reach a map's copy of the bundle.
- Read Dev / Creator / Homie Steam IDs from api.reskate.dev instead of the hardcoded list
- Creator (red) and Homie (gold) tags and hoodie/board colours; Admin tag is now pink
- Global bans: lobby hosts and dedicated servers turn banned players away
("global_bans": false in ReSkateServer.json opts a server out)
- SPECIAL menu page for listed players to hide their own tag and items
- Fix hoodie and board keeping the animated colour after the animation turns off
- Multiplayer protocol 39 -> 40
Skater slot, menu and throwdown fixes; MY MODS rework; lobby parties
Saved skaters (Extension/Customization, Extension/Skater)
- The selected slot, and slots before it, came up as the standard
skater after a start or map change. The game asks for a slot's saved
outfit once per level, and that happened before the cosmetics catalog
could be read. The first selection now waits for the catalog (60 s at
most), and later map changes check against the catalog already read.
- The retry from abdda77 is removed: the record it wrote to is a
temporary on the game's stack, so it could never work.
Pause menu (Extension/UI/NativeMenu)
- ReSkate's entries stopped working after enough map changes: each
level change took new slots in a fixed table of 1024 for the same
buttons. A button now keeps its slot, and a full table hands out the
slot that has gone longest unused.
Throwdowns (Extension/Throwdowns, Runtime/runtime_init.cpp)
- Multiplayer crashed when someone placed a throwdown after a map
change. The event types are freed with the level but were cached for
the process. They are now cleared when a level is left, checked
before use, and looked for again in each level.
MY MODS (Launcher/gui_mods.cpp, gui.cpp)
- Search, a filter and an order; a switch per mod; a menu per row.
- Multi-select with tick boxes, Shift-click and Ctrl+A, and a bar to
enable, disable, move, update or uninstall everything ticked.
Lobby parties (Extension/Multiplayer/Session)
- A lobby is no longer one party. Its host keeps parties the way a
dedicated server does: invite, accept, join, leave, kick, promote,
open and close, with "/p" party chat. The "Lobby party" switch is
gone. Protocol version unchanged (38).
Mod merge (Engine/Vfs), follow-ups to e715b9b
- Files rewritten into the merged patch are tracked by position, so
another mod's added assets no longer shift the lookup.
- Two mods adding different assets under one name is now noted, with
whose copy the maps get.
- mod_merge_unshift_tests compiles with the launcher tests on.
A mod that adds songs can ship reskate-music.json
({"schema":1,"playlists":[{"name","songs":["artist - title", ...]}]}). Each
playlist is merged into the game's music screen the way a content-cache
station is, under the id "mod:<mod folder>:<name>", with its songs matched
against the songs the game has registered; entries that match nothing are
ignored and a playlist left empty is dropped. A file of the wrong shape is
logged and skipped.
The file is read by parse_music_playlists in Engine/Vfs/mod_music, which a new
test covers (ids, which entries survive, the limits, and that a malformed
document is refused). The format is described in the README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Song records in the content cache carry a field-10 message (10 artist, 11 title,
12 cdn:/ cover art). Read the art per song and send it as the song's
presentation field 12, resolved to the CDN rendition like a station's art, so
the game's music screen and now-playing toast show covers instead of a black
square. The catalogue test pins one song's art.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The music menu listed only the playlists the game's own music assets declare.
The licensed stations (San Van Soundtrack, 80's Mixtape, ...) live in the
content cache's music chunk: a record with the station id (field 1), its tracks
(repeated field 2, "Artist - Title") and a message with its display name and
cdn:/ artwork (field 10, parts 10 and 11). Read them into Catalogs, merge their
membership into the music catalogue (only songs the game registered, so no
unknown id is referenced), and send the name and artwork with each playlist.
The catalogue test pins two stations from the cache.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A fixture of a made-up game, a level mod with its own level superbundle holding
a copy of a bundle, and an asset mod that changes one asset to name a new song
and adds the song, its wave, the wave's resource and one asset nothing names.
The merged copy in the map's TOC must gain exactly the three the changed asset
leads to, whichever mod has priority. Against upstream's merge it fails with the
merge's own warning that the mod's additions to the bundle are missing.
The documents are built in the test, so it needs no game files.
A second scenario puts two mods' copies in the adder's own TOC and checks nothing is
carried into it and the merged bundle is just the union.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Two fixes taken from worthlessnorms' pull request 16. The bug it was
opened for, rewritten copies read back from the mod's folder, was fixed
separately in 6fd3845.
Mod merge (Engine/Vfs/mod_merge_cas_store.cpp, mod_merge_combine.cpp)
- To combine two mods' edits the merge reads each mod's copy from the
mod's own folder, which means turning its placement in the merged
patch back into the mod's archive and offset. It took the first of the
mod's blocks in that patch archive. A mod added while the game runs
has all its archives appended to the patch's archive 1, so a copy in
its second archive was looked for in its first, the read failed, and
the asset kept the highest-priority copy.
- unshift() picks the block the offset falls in. Of two blocks that
start at the same byte the later one is used: the earlier is an empty
archive.
- Reproduced with a cosmetic mod rebuilt as two archives and added to a
running merge: cas_main_bundlereftable "could not be combined (Cannot
read ...\cas_01.cas)" before, "combined 2 edits (101 preset(s))"
after. A launch merge gives every archive an index of its own and was
not affected; 40 installed mods merge exactly as before.
- mod_merge_unshift_tests covers an archive with its own index, three
appended blocks, bytes before the first block, and an empty archive.
Tests (CMakeLists.txt, Extension/Multiplayer/Test/mesh_tests.cpp)
- multiplayer_lobbies did not link: steam_lobbies.cpp calls
contains_bad_words and the target did not link dingosdk_word_filter.
- multiplayer_mesh did not link: four functions the session code calls
had no stub. Two are the ones the pull request adds
(update_developer_hoodie, remote_skater_generation); the developer
board has since added update_developer_board and remote_board_entity.
- Both targets build and their tests pass.
Co-authored-by: worthlessnorms <110003169+worthlessnorms@users.noreply.github.com>
With several cosmetic mods installed the game crashed as soon as an
equipped custom item came into view, and so on every launch after it
was equipped.
Cosmetic presets (Engine/Resource/bundle_ref_table.*)
- The character bundle-reference table says which bundle holds each
preset, under the preset's full path and under its leaf name, one row
per hash. Mods number their presets alike in different folders
(cust_tops/1_ap, cust_bottoms/1_ap), so two mods can share a leaf
name. Adding the second preset threw "collides with an existing
lookup", the merge fell back to the highest-priority mod's copy of
the table, and every other cosmetic mod's presets were unregistered.
The game then built the skater with a preset it could not find and
called through a bad pointer.
- A preset whose leaf name is taken is now added under its full path
only; the leaf name stays with the preset that had it. Only a clash
of full paths still fails. MergedTable::shadowed lists each such
preset with the one that holds its name.
Merge log (Engine/Vfs/mod_merge_combine.cpp)
- Each mod with shadowed presets gets a line naming how many and an
example pair, so an item that shows the wrong preset can be traced to
the mods that clash.
Checked against 39 installed mods, 15 of them cosmetic: the table
combines 274 presets where it used to keep one mod's, all reachable by
full path and 267 by leaf name, with the game's own 6264 rows unchanged.
The seven left without a leaf row are a naming clash between the mods
themselves (two Carhartt packs, a Zero tee and deck); whether the game
finds those by full path is not known.
bundle_ref_table_tests takes an optional second mod and checks that two
mods' presets are all added, in either order, and that a shared leaf
name is reported with the preset that holds it.
A blank launcher window (Launcher/gui_renderer.cpp, Launcher/gui.cpp)
Reported as a launcher that showed nothing, from someone with a GT 1030.
The log named the card it had used: Intel HD Graphics 4600, the onboard
chip beside it.
- D3D12CreateDevice(nullptr, ...) takes whichever adapter DXGI lists
first, which on a PC with onboard graphics beside a real card is the
onboard one. It now walks EnumAdapterByGpuPreference with
HIGH_PERFORMANCE, skips software adapters and takes the first that
gives a device, as a game would. The old default adapter and WARP
remain behind it, so no working setup loses its device.
- That chip's Direct3D 12 driver cannot build a pipeline, and the ImGui
backend creates one on the first frame while ignoring whether it
worked (imgui_impl_dx12.cpp:792). Every draw then does nothing and the
window shows only ClearRenderTargetView's colour, which is near black.
Untextured shapes sample the font atlas's white pixel, so a failed
atlas looks identical -- the drawn background disappears with the
text. The pipeline and font texture are built in init now and checked,
and a failure says to update the driver and that the card may be below
what ReSkate and skate. need.
- None of this was diagnosable, because the renderer logged nothing at
all. It now records the adapter, its memory and whether it is the
software fallback; the window size, scale, system DPI and work area;
the font atlas size and whether it built; and the first frame drawn.
Those four lines are what turned "blank window, healthy log" into the
two bugs above, and they separate a missing device from a missing
pipeline from a window that draws and is still black.
World layers (Engine/Vfs/world_layer_scan.cpp)
- CoreGameAssets had a row like any other, and switching it off took the
world away with no clue which row had done it. essential_layer keeps
its node, so its children are still found and still switchable, and
drops its row -- which removes it from the menu, from "world <key>
off" and from layer sync at once, rather than hiding it in one place
and leaving it reachable from another.
- Choices are stored by row key, not by index, so no saved setting
shifts, and an "off" already stored for it has no row to apply to: a
profile that turned it off fixes itself on the next launch.
- The cache stamp goes to 2, so a cache written before this is rebuilt
instead of keeping the row alive on existing installs.
- The scan test asserted one row per node, which this breaks on purpose.
It now checks the counts differ, that no row's key holds
"coregameassets", and that the node is still there. Against a real
install: 262 rows to 261, six maps, passing.