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

@ -12,9 +12,6 @@ require "wired_switch"
require "wired_door"
require "dialogue_manager"
require "generic_moveable"
require "generic_sticky"
require "generic_skewer"
require "generic_spiky"
require "generic_unmoveable"
require "generic_player_blocker"
require "note"
@ -772,6 +769,17 @@ function Room:createEntity(class, color, x, y, data)
return entity
end
-- Legacy: adhesion used to be baked into separate asset classes
-- (spiky_/skewer_/sticky_<box>). Collapse them to the base asset carrying an
-- adhesion string so old .sav files still load. spiky/old-sticky both become
-- the new soft "sticky"; skewer becomes the rigid "gluey".
local adhesion = data.adhesion
local legacyAdhesion = { spiky = "sticky", skewer = "gluey", sticky = "sticky" }
for prefix, mapped in pairs(legacyAdhesion) do
local base = class:match("^" .. prefix .. "_(.+)")
if base then class = base; adhesion = adhesion or mapped; break end
end
local props = globalAssetProperties and globalAssetProperties[class]
local behavior = props and props.class or "immoveable"
local entity
@ -829,13 +837,7 @@ function Room:createEntity(class, color, x, y, data)
entity = GenericPlayerBlocker:new(x, y, color, sprite, cellShape)
self:registerPlayerBlocker(entity, color)
elseif behavior == "moveable" then
entity = GenericMoveable:new(x, y, color, sprite, class, cellShape)
elseif behavior == "sticky" then
entity = GenericSticky:new(x, y, color, sprite, class, cellShape)
elseif behavior == "skewer" then
entity = GenericSkewer:new(x, y, color, sprite, class, cellShape)
elseif behavior == "spiky" then
entity = GenericSpiky:new(x, y, color, sprite, class, cellShape)
entity = GenericMoveable:new(x, y, color, sprite, class, cellShape, adhesion)
else
entity = GenericUnmoveable:new(x, y, color, sprite, cellShape)
end
@ -1038,7 +1040,7 @@ function Room:attemptDelete(mouseX, mouseY, color)
return self:attemptDeleteAtCell(self:getCellFromMousePos(mouseX, mouseY), color)
end
function Room:attemptPlaceAtCell(name, props, cell, color, text)
function Room:attemptPlaceAtCell(name, props, cell, color, text, adhesion)
-- Notes are colourless, single-instance annotations (no per-channel sprites),
-- so they place once carrying their text rather than one copy per colour. One
-- note per cell: drop any note already here before adding the new one.
@ -1084,7 +1086,7 @@ function Room:attemptPlaceAtCell(name, props, cell, color, text)
-- additive blend combines the channels into full color
for _, c in ipairs(colors) do
if props.sprites[c] then
self:createEntity(name, c, cell.x, cell.y)
self:createEntity(name, c, cell.x, cell.y, { adhesion = adhesion })
end
end
self:tick()
@ -1181,6 +1183,7 @@ function Room:serialize(includePlayers)
local pos = entity:getGridPos()
local object = { class = class, color = color, x = pos.x, y = pos.y }
if entity.circuitId then object.circuit = entity.circuitId end
if entity.adhesion then object.adhesion = entity.adhesion end
table.insert(objects, object)
end