loot overlay: speak the contents of visible containers
The tutorial teaches "press s to see the contents of containers", and it was silent. For a sighted player s sets levelWindow.itemsHighlightEnabled, which draws the top item's icon on every loot-bearing tile in FoV. We watch that flag rather than binding s ourselves -- it is game-claimed, and keybindings.getCommand() ignores modifiers -- and speak on its rising edge, so the player's own binding, hold vs toggle mode and the toolbar icon all work. The flag is checked both at end of frame and inside the keypressed wrapper, because in hold mode a quick tap goes up and down within one frame. Reads the whole tile.loot list, not just the icon's top entry, nearest first, with each container pushed to the console history for [ ] review. 71 testbed checks, 0 failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dddd8ed9de
commit
93ef26349a
@@ -753,6 +753,113 @@ local function scenario()
|
||||
print("[testbed] viewwatch: " .. tostring(text))
|
||||
end
|
||||
|
||||
|
||||
------------------------------------------------------------------
|
||||
-- Loot overlay ("s"): speak the contents of visible containers
|
||||
--
|
||||
-- The tutorial house is the awkward case: chest_tutorial has no loot
|
||||
-- by design ("you find nothing of use for now"), so a live scan here
|
||||
-- may legitimately be empty. The phrasing is therefore asserted on
|
||||
-- the pure seam with a synthetic container, and the live scan is used
|
||||
-- for the FoV invariant and for "it does not throw".
|
||||
------------------------------------------------------------------
|
||||
if currentState() == "ingame" then
|
||||
local lootoverlay = require("accessibility.lootoverlay")
|
||||
|
||||
local found, px, py = lootoverlay.scan()
|
||||
check("loot overlay scan returns a list", type(found) == "table",
|
||||
type(found) .. ", " .. tostring(found and #found) .. " containers")
|
||||
|
||||
if found then
|
||||
local ok, level = pcall(require, "models.world")
|
||||
level = ok and level.currentLevel
|
||||
|
||||
-- Invariant 4: the overlay draws only inside current FoV, and so
|
||||
-- must we -- this must never become an x-ray of sealed rooms.
|
||||
local leaked
|
||||
for _, entry in ipairs(found) do
|
||||
if not (level and level.FoV and level.FoV[entry.x]
|
||||
and level.FoV[entry.x][entry.y]) then
|
||||
leaked = entry.container
|
||||
end
|
||||
end
|
||||
check("loot overlay reports nothing outside FoV", leaked == nil,
|
||||
leaked and ("leaked " .. tostring(leaked)) or "clean")
|
||||
|
||||
local liveBlocks = lootoverlay.blocks(found, px, py)
|
||||
check("loot overlay always says something",
|
||||
type(liveBlocks) == "table" and #liveBlocks > 0,
|
||||
table.concat(liveBlocks or {}, " | "))
|
||||
print("[testbed] loot overlay: " .. table.concat(liveBlocks or {}, ". "))
|
||||
end
|
||||
|
||||
-- Synthetic container, two tiles east of the player: name, quantity,
|
||||
-- position, and the trailing full stop stripped off the description.
|
||||
local synthetic = {{
|
||||
x = px + 2, y = py, distance = 2,
|
||||
container = "A wooden chest.",
|
||||
items = {"Ivy leaves, 3", "A torch"},
|
||||
}}
|
||||
local blocks = lootoverlay.blocks(synthetic, px, py)
|
||||
local text = table.concat(blocks, ". ")
|
||||
|
||||
check("loot overlay names the container and where it is",
|
||||
text:find("A wooden chest, 2 tiles east", 1, true) ~= nil, text)
|
||||
check("loot overlay lists the contents, not just the top item",
|
||||
text:find("Ivy leaves, 3", 1, true) and text:find("A torch", 1, true) ~= nil,
|
||||
text)
|
||||
check("loot overlay says so when there is nothing to see",
|
||||
lootoverlay.blocks({}, px, py)[1] == "No loot in view",
|
||||
tostring(lootoverlay.blocks({}, px, py)[1]))
|
||||
|
||||
-- Live scan of the REAL grid, which the prologue cannot otherwise
|
||||
-- exercise: chest_tutorial is empty by design, so without this the
|
||||
-- only path ever run is the "nothing here" one. Loot is planted on
|
||||
-- the tile the player is standing on (guaranteed visible) and put
|
||||
-- back immediately -- this must not alter the playthrough.
|
||||
do
|
||||
local ok, world = pcall(require, "models.world")
|
||||
local level = ok and world.currentLevel
|
||||
local tile = level and level.grid[px] and level.grid[px][py]
|
||||
and level.grid[px][py][1]
|
||||
|
||||
if tile then
|
||||
local realLoot = tile.loot
|
||||
tile.loot = {{templateId = "ivy_leaves", quantity = 3}}
|
||||
|
||||
local planted, ppx, ppy = lootoverlay.scan()
|
||||
local plantedText = table.concat(
|
||||
lootoverlay.blocks(planted, ppx, ppy) or {}, ". ")
|
||||
|
||||
tile.loot = realLoot
|
||||
|
||||
check("loot overlay finds loot on the real grid",
|
||||
#planted == 1, #planted .. " containers")
|
||||
-- Loot entries are item PARAMS, not items: the name has to come
|
||||
-- from the template table, which is the part most likely to move.
|
||||
check("loot overlay resolves item names from templates",
|
||||
plantedText:lower():find("ivy", 1, true) ~= nil, plantedText)
|
||||
check("loot overlay reports the quantity",
|
||||
plantedText:find(", 3", 1, true) ~= nil, plantedText)
|
||||
check("loot overlay restored the tile it borrowed",
|
||||
tile.loot == realLoot, "restored")
|
||||
end
|
||||
end
|
||||
|
||||
-- The probe the whole feature hangs on: states/ingame's levelWindow.
|
||||
check("loot overlay can still reach levelWindow",
|
||||
lootoverlay.probeStatus() == "ok", tostring(lootoverlay.probeStatus()))
|
||||
|
||||
-- And end to end: the game's own key must reach the rising edge.
|
||||
local history = require("accessibility.history")
|
||||
local baseline = history.count()
|
||||
press("s", "loot overlay key")
|
||||
check("the loot key did not leave gameplay", currentState() == "ingame",
|
||||
"got " .. currentState())
|
||||
print("[testbed] loot key added " .. (history.count() - baseline) ..
|
||||
" history lines (0 is correct when nothing is lootable nearby)")
|
||||
end
|
||||
|
||||
if sliderTested then
|
||||
check("slider announces value changes", true, "via ingame_wait")
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user