Files
alchemist-accessibility/accessibility/patches/popups.lua
T
Accessibility ModandClaude Opus 5 8b1eb44c38 Phase 2: tile examination and surroundings
Game-file footprint unchanged: still one line in main.lua.

- describe.lua: shared spatial formatting. 8-point compass and Chebyshev
  distance, since the game moves 8-directionally and Euclidean would overstate
  diagonal travel. Verbosity setting, because sweeping eight neighbours and
  inspecting a single tile want different amounts of detail.
- states/ingame_observe.lua: narrates the game's existing examine cursor.
- surroundings.lua: probe one direction, scan all eight, or report location,
  without moving anything or touching game state.
- patches/popups.lua: wraps inventoryCursorPopup.set, covering inventory, craft,
  refine and trade in a single patch.

Q3 resolved as augment rather than relay: the game's descriptions carry no
position, which is fine on screen and useless by ear, so distance and direction
are appended.

Examine mode is polled rather than driven by levelCursorPopup. That popup is set
from an animation-completion callback, so unless the player has enabled the
game's quickExaminationPopup option the text arrives a full keypress late -- in
testing you heard the previous tile while standing on the next. Polling
level.cursorX/Y is same-frame and setting-independent.

hotkeys.isCtrlDown/isShiftDown are now overridable fields rather than direct
love.keyboard calls: real hardware state cannot be faked by love.event.push, so
without that seam every modifier-gated hotkey would be untestable.

11/11 testbed checks, history unit tests passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 21:26:35 +02:00

77 lines
2.1 KiB
Lua

-- Patch: views/levelCursorPopup.lua and views/inventoryCursorPopup.lua
--
-- `inventoryCursorPopup.set` is the convergence point for focus text in four
-- screens: inventory, craft, refine and trade. Patching it narrates all four
-- without touching any state file.
--
-- inventoryCursorPopup.set(lines, x, y, alignX, alignY) -- lines is a TABLE
-- of {text=, icon=}
--
-- NOTE: levelCursorPopup (the examine cursor) is deliberately NOT handled here.
-- It is set from an animation-completion callback, so its text lands a keypress
-- late unless the player enables the game's "quickExaminationPopup" option.
-- accessibility/states/ingame_observe.lua polls the cursor position instead,
-- which is immediate and setting-independent.
local speech = require("accessibility.speech")
-- Suppress repeats: the setter is called on every mouse move, often with
-- unchanged content.
local lastInventoryText
local function linesToText(lines)
if type(lines) ~= "table" then
return nil
end
local parts = {}
for _, line in ipairs(lines) do
if type(line) == "table" and line.text then
parts[#parts + 1] = line.text
elseif type(line) == "string" then
parts[#parts + 1] = line
end
end
if #parts == 0 then
return nil
end
return table.concat(parts, ". ")
end
return function()
----------------------------------------------------------------------------
-- inventoryCursorPopup: inventory, craft, refine, trade
----------------------------------------------------------------------------
local inventoryPopup = require("views.inventoryCursorPopup")
local realInventorySet = inventoryPopup.set
if not realInventorySet then
error("views.inventoryCursorPopup has no set function -- game structure changed")
end
inventoryPopup.set = function(lines, ...)
local result = realInventorySet(lines, ...)
pcall(function()
local text = linesToText(lines)
if not text then
lastInventoryText = nil
return
end
text = speech.normalise(text)
if not text or text == lastInventoryText then
return
end
lastInventoryText = text
speech.say(text, true)
end)
return result
end
end