Replace box variants with a moveable adhesion property

Collapse the spiky/sticky/skewer box-variant assets into a single per-
instance `adhesion` property on any moveable block, chosen in the editor
via a dropdown next to the colour picker and stored in the .sav alongside
the class.

- spiky -> sticky: soft grab on every true edge (goopy shader)
- skewer -> gluey: rigid weld on every true edge (spiked outline)
- old trailing-face sticky is removed; its saves fold into new sticky

Move the spike geometry + drawGlueySprite into shaders.lua, fold drawing
and the adhesion flag into GenericMoveable, and delete the three
Generic{Sticky,Spiky,Skewer} classes and box_variants.lua. Room:createEntity
normalises legacy spiky_/skewer_/sticky_ class prefixes so old saves load,
and serialization emits `adhesion`. World/map previews and the movement
proxy follow the rename.

Backfill rooms/*.sav to the new format via tools/backfill_adhesion.lua
(41 objects across 8 files). Update tests to the two-mode model; the two
tests asserting the removed lateral-shear limitation are dropped and a
legacy-load migration test added. 27/27 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-08-21 16:34:01 -04:00
parent c348b81a93
commit 7d84f595e4
25 changed files with 4010 additions and 3797 deletions

View file

@ -0,0 +1,60 @@
-- One-off migration: rewrite rooms/*.sav from the old baked-in adhesion classes
-- (spiky_/skewer_/sticky_<box>) to the new model of a plain base class carrying
-- an `adhesion` property. spiky and old-sticky both become the new soft "sticky";
-- skewer becomes the rigid "gluey". Run once from the project root: lua tools/backfill_adhesion.lua
loadstring = loadstring or load -- TSerial.unpack still calls loadstring (removed in Lua 5.4+)
package.path = "./?.lua;" .. package.path
require "libs/TSerial"
local dir = "rooms"
local adhesionForPrefix = { spiky = "sticky", skewer = "gluey", sticky = "sticky" }
local function savFiles()
local files = {}
local pipe = io.popen('ls "' .. dir .. '"')
for name in pipe:lines() do
if name:match("%.sav$") then table.insert(files, name) end
end
pipe:close()
return files
end
local function readFile(path)
local handle = assert(io.open(path, "r"))
local contents = handle:read("*a")
handle:close()
return contents
end
local function writeFile(path, contents)
local handle = assert(io.open(path, "w"))
handle:write(contents)
handle:close()
end
local totalFiles, totalObjects = 0, 0
for _, file in ipairs(savFiles()) do
local path = dir .. "/" .. file
local saveTable = TSerial.unpack(readFile(path))
local migrated = 0
for _, object in ipairs((saveTable and saveTable.objects) or {}) do
if type(object.class) == "string" then
for prefix, adhesion in pairs(adhesionForPrefix) do
local base = object.class:match("^" .. prefix .. "_(.+)")
if base then
object.class = base
if not object.adhesion then object.adhesion = adhesion end
migrated = migrated + 1
break
end
end
end
end
if migrated > 0 then
writeFile(path, TSerial.pack(saveTable, nil, true))
totalFiles = totalFiles + 1
totalObjects = totalObjects + migrated
print(string.format("%-28s migrated %d object(s)", file, migrated))
end
end
print(string.format("done: %d object(s) across %d file(s)", totalObjects, totalFiles))