items: read the details of the focused item
The item grids said which item you were on ("a torch, slot 3") but nothing
about what it was -- no description, no condition, and no "what does this
refine into", which is the whole of alchemy.
The game already composes all of it in one place: item.getPopupMessage()
(modules/item.lua:199) returns the cursor popup's line list, so read that
rather than re-deriving anything from templates.
It also already gates it behind a dwell -- the popup only fades in after
examinationPopupFadeInDuration (0.4s), so a sighted player who arrows past a
slot never sees the details either. Mirror that instead of inventing a
modality: stop on an item for 0.6s and the details speak themselves, queued.
"=" reads them on demand, Shift+= keeps the name line too. ("r" was the
obvious mnemonic but is a game-claimed scancode, and getCommand() dispatches
on scancode ignoring modifiers.)
New generic hook descriptor.details() returning a LIST of blocks, so each is
also pushed into the console history and [ ] \ walk a long description back
afterwards -- the journal page trick. Only _itemgrid.lua implements it so far.
The other half is the hint panel craft/refine/trade draw permanently beside
the grid, which the popup never contains: recipe components, and price, which
appears nowhere else in the game. The three panels share a shape, so one
reader covers them. Affordability survives only as a red colour in the hint
table and decides whether enter does anything, so it is decoded and spoken.
59 testbed checks, 0 failures: a synthetic ivy_leaves item as the version-bump
canary for getPopupMessage, hasUpvalue over every hint-panel local, four
assertions on the pure settleStep, and an end-to-end "=" press. Not yet
exercised against a live item -- the prologue inventory is empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c841bcc68d
commit
a00f327c68
@@ -464,6 +464,71 @@ local function scenario()
|
||||
end
|
||||
end
|
||||
|
||||
-- Item DETAILS (the `=` key and the settle timer). Same problem as the
|
||||
-- action menu above: the prologue inventory is empty, so there is no real
|
||||
-- item to focus. The fragile parts are (a) item.getPopupMessage still
|
||||
-- existing and still carrying the description and the refine yields, and
|
||||
-- (b) the hint-panel locals in craft/refine/trade keeping their names.
|
||||
-- Both are checkable without a live screen.
|
||||
do
|
||||
local itemdetails = require("accessibility.itemdetails")
|
||||
local newItem = require("modules.item")
|
||||
|
||||
-- ivy_leaves has a description AND both a grind and a distill recipe,
|
||||
-- so one item exercises every branch of getPopupMessage worth having.
|
||||
local item = newItem{templateId = "ivy_leaves", quantity = 3}
|
||||
local blocks = itemdetails.blocks(item)
|
||||
local joined = table.concat(blocks or {"<nil>"}, " | ")
|
||||
|
||||
check("item details produce blocks", type(blocks) == "table" and #blocks > 0,
|
||||
"blocks=" .. joined)
|
||||
check("item details include the description",
|
||||
joined:find("weak poison", 1, true) ~= nil, joined)
|
||||
check("item details include the refinement yields",
|
||||
joined:find("Yields", 1, true) ~= nil
|
||||
and joined:find("mortar", 1, true) ~= nil, joined)
|
||||
check("item details drop the name line already spoken by read()",
|
||||
blocks ~= nil and blocks[1] ~= nil
|
||||
and blocks[1]:find("bunch of ivy", 1, true) == nil,
|
||||
tostring(blocks and blocks[1]))
|
||||
check("verbose item details keep the name line",
|
||||
(itemdetails.blocks(item, true) or {""})[1]:find("ivy", 1, true) ~= nil)
|
||||
|
||||
-- Hint-panel canary. These locals hold the recipe cost, the refinement
|
||||
-- output and (in trade) the price, none of which appear in the cursor
|
||||
-- popup -- a rename would make them silently vanish.
|
||||
local hintLocals = {
|
||||
craft = {"hintDescription", "componentHints"},
|
||||
refine = {"hintDescription", "outputName", "outputQuantityText",
|
||||
"budgetNameText", "budgetQuantityText"},
|
||||
trade = {"componentHints", "budgetNameText", "budgetQuantityText"},
|
||||
}
|
||||
|
||||
for stateName, names in pairs(hintLocals) do
|
||||
local stateTable = menus.getStateTable(stateName)
|
||||
for _, name in ipairs(names) do
|
||||
check(stateName .. " hint panel still has local '" .. name .. "'",
|
||||
stateTable ~= nil and menus.hasUpvalue(stateTable, name))
|
||||
end
|
||||
end
|
||||
|
||||
-- The settle timer: due exactly once per focus key, and re-armed by a
|
||||
-- change. Without "exactly once" a stationary cursor would re-speak the
|
||||
-- description every frame.
|
||||
local details = require("accessibility.details")
|
||||
details.reset()
|
||||
check("settle does not fire before the delay",
|
||||
details.settleStep("a", 0.1) == false)
|
||||
check("settle fires once the delay has passed",
|
||||
details.settleStep("a", details.config.settleDelay) == true)
|
||||
check("settle does not fire twice for the same focus",
|
||||
details.settleStep("a", 10) == false)
|
||||
check("a focus change re-arms the settle",
|
||||
details.settleStep("b", 0.01) == false
|
||||
and details.settleStep("b", 10) == true)
|
||||
details.reset()
|
||||
end
|
||||
|
||||
-- The journal's whole point is the page of text on the right-hand side; the
|
||||
-- list of titles on the left is just an index into it. Announcing only the
|
||||
-- title (which is what it used to do) reads as "there is an entry here"
|
||||
@@ -494,6 +559,24 @@ local function scenario()
|
||||
", spoken=" .. tostring(spoken))
|
||||
end
|
||||
|
||||
-- The `=` key end to end. The prologue inventory is empty, so what this
|
||||
-- proves is the wiring: the key reaches details.speak, is consumed rather
|
||||
-- than falling through to the game, and an empty slot says "No details"
|
||||
-- instead of nothing at all (silence being indistinguishable from a
|
||||
-- dropped keypress). The text itself is covered by the synthetic item above.
|
||||
local function checkDetailsKey()
|
||||
local details = require("accessibility.details")
|
||||
|
||||
check("empty slot has no details",
|
||||
details.get("inventory") == nil)
|
||||
|
||||
press("=", "read item details")
|
||||
wait(5)
|
||||
|
||||
check("the details key did not leave the inventory",
|
||||
currentState() == "inventory", "state is " .. tostring(currentState()))
|
||||
end
|
||||
|
||||
-- Phase 4: the menu screens reachable from gameplay.
|
||||
--
|
||||
-- Each is opened, navigated once, and closed. `back` is bound to x/escape/
|
||||
@@ -502,7 +585,7 @@ local function scenario()
|
||||
{key = "j", state = "journal", nav = {"down", "right"}, check = checkJournalReadsPage},
|
||||
{key = "c", state = "character", nav = {"right", "right"}},
|
||||
{key = "a", state = "actions_menu", nav = {"down"}},
|
||||
{key = "i", state = "inventory", nav = {"right", "down"}},
|
||||
{key = "i", state = "inventory", nav = {"right", "down"}, check = checkDetailsKey},
|
||||
}
|
||||
|
||||
for _, screen in ipairs(screens) do
|
||||
|
||||
Reference in New Issue
Block a user