inventory: speak the item action menu

Pressing enter/z on an inventory item plays a click and, until now, said
nothing. The click is sound.select: the menu WAS opening every time.

inventory.keypressed -> inventoryWindow.enableItemMenu() builds a second,
nested selection over the grid (modules/item/itemMenu.lua) listing the
actions that item currently allows, filtered from itemActions.lua by
per-action conditions: Activate, Wield/Unwield, Wear/Take off, Switch
on/off, Drink, Eat, Apply, Read, Open, Prepare for throwing/Pack away.
Up/down move in it, enter activates, any other key cancels. None of it
goes through console.print or the cursor popup, so it was silent -- and
since the game has no separate equipment screen, this menu is the only
way to equip anything. Reported by Talon: the torch quest was unplayable.

_itemgrid.lua now checks window.itemMenu first and keeps the grid cell
quiet underneath, since the menu is modal over it. The menu is a closure
with no getters, so lines and currentSelectedAction are read as upvalues
of its own draw(). The item name leads only when the menu opens, and the
"just opened" memory is the menu table itself rather than a flag, which
keeps read() idempotent -- a flag would be consumed by the focus poll and
the real announcement would come out headless.

Items also now read "equipped", which is part of the focus key on
purpose: after Switch on the menu closes with the cursor unmoved, so
otherwise the re-read is suppressed as unchanged and the action still
feels silent. Now: "Switch on" -> "a torch, equipped, slot 3".

New patch (the 6th) wraps the inventoryWindow factory for the dead case:
when getActionsList returns nil -- an item with no applicable actions --
the game does literally nothing, not even the click, which by ear is
indistinguishable from a dropped keypress. Says "<item>, no actions".

The prologue inventory is empty, so the live path is unverified; the
testbed instead builds an itemMenu from a synthetic item and asserts the
two upvalue names still resolve. That is the version-bump canary for this
descriptor, not proof of the wording. 38 checks, 0 failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Accessibility Mod
2026-08-02 02:59:21 +02:00
co-authored by Claude Opus 5
parent 7ce429c2fb
commit 0df3842bf1
5 changed files with 195 additions and 6 deletions
+54
View File
@@ -0,0 +1,54 @@
-- Patch: views/inventoryWindow.lua -- the "this item has no actions" case.
--
-- Pressing enter/z on an inventory cell calls window.enableItemMenu(), which
-- builds the item's action list via modules/item/getActionsList.lua. That list is
-- filtered by per-action `condition`s, and getActionsList returns NIL when none
-- of them apply -- a plain reagent, say, which you can neither wield, drink, read
-- nor switch on. newItemMenu then returns nil, enableItemMenu returns nil, and
-- the game does *literally nothing*: no menu, and not even the sound.select click
-- it plays on success.
--
-- Sighted players see the absence of a popup. By ear that is indistinguishable
-- from a dropped keypress, so this says so out loud. The successful case is NOT
-- announced here -- accessibility/states/_itemgrid.lua reads the open menu.
--
-- ORDERING: like patches/slider.lua this replaces package.loaded, so it only
-- affects requires that happen afterwards. Safe for the same reason: states are
-- loaded lazily by states.lua, and applyPatches() runs before the self-test.
local speech = require("accessibility.speech")
return function()
local newInventoryWindow = require("views.inventoryWindow")
if type(newInventoryWindow) ~= "function" then
error("views.inventoryWindow is not a factory function -- game structure changed")
end
package.loaded["views.inventoryWindow"] = function(...)
local window = newInventoryWindow(...)
local realEnable = window.enableItemMenu
if type(realEnable) ~= "function" then
return window
end
window.enableItemMenu = function(...)
local result = realEnable(...)
if not result then
pcall(function()
local ok, item = pcall(window.getItemOnCursor)
if not ok or not item then
return
end
speech.say((item.name or "This item") .. ", no actions", true)
end)
end
return result
end
return window
end
end