-- accessibility/init.lua — mod entry point. -- -- This is the ONLY thing the game calls into. main.lua gains exactly one line: -- -- require("accessibility").initialize() -- -- placed after console.prepare() and before the first states.switch(). By that -- point templates are preprocessed, keybindings are loaded, states.setup() has -- run, and the console canvas exists — everything we patch is ready. -- -- Everything here is wrapped so that a bug in the mod degrades to "no speech" -- rather than taking the game down with it. local accessibility = {} local speech = require("accessibility.speech") local stateNames = require("accessibility.stateNames") local hotkeys = require("accessibility.hotkeys") local walltones = require("accessibility.walltones") local initialized = false -------------------------------------------------------------------------------- -- Patch registry -- -- Later phases register their patches here. Each entry is {name, fn}; fn is called -- inside a pcall during initialize(), and a failure disables only that patch. -------------------------------------------------------------------------------- local patches = {} function accessibility.registerPatch(name, fn) patches[#patches + 1] = {name = name, fn = fn} end local patchResults = {} local function applyPatches() for _, patch in ipairs(patches) do local ok, err = pcall(patch.fn) patchResults[patch.name] = ok and "ok" or tostring(err) if not ok then print("[accessibility] patch '" .. patch.name .. "' FAILED: " .. tostring(err)) end end end -- Startup self-test output. This is the version-bump canary: when the game -- updates and something we hook has moved, it shows up here immediately rather -- than as silence three screens into a playthrough. function accessibility.selfTest() print("[accessibility] --- self test ---") print("[accessibility] TTS: " .. (speech.isAvailable() and "available" or ("UNAVAILABLE (" .. tostring(speech.getFailureReason()) .. ")"))) if speech.isAvailable() then -- Distinguishes "DLL loaded" from "actually reached a screen reader or SAPI". print("[accessibility] speech channel: " .. (speech.hasSpeech() and "yes" or "NO — nothing will be audible")) end for name, result in pairs(patchResults) do print("[accessibility] patch " .. name .. ": " .. result) end print("[accessibility] --- end self test ---") end -------------------------------------------------------------------------------- -- Core hooks into states.lua -------------------------------------------------------------------------------- local function hookStates() local states = require("states") -- Screen changes: announce the new screen. local realSwitch = states.switch states.switch = function(newState, params) local result = realSwitch(newState, params) -- After the switch, so a state's open() has already set up its focus. pcall(function() local name = stateNames[newState] or newState speech.say(name, true) end) return result end -- Per-frame speech flush. main.lua calls states.update(dt) via a table lookup, -- so wrapping the field here avoids editing love.update. local realUpdate = states.update states.update = function(dt) local result = realUpdate(dt) pcall(walltones.update) pcall(speech.flush) return result end -- Global hotkey layer. Ours are consumed; everything else falls through to the -- game untouched. local realKeypressed = states.keypressed states.keypressed = function(key, scancode) local consumed = false local ok, result = pcall(hotkeys.keypressed, key, scancode) if ok then consumed = result end if consumed then return end pcall(hotkeys.onGameKey) return realKeypressed(key, scancode) end end -------------------------------------------------------------------------------- -- Entry point -------------------------------------------------------------------------------- function accessibility.initialize() if initialized then return end initialized = true accessibility.registerPatch("console", require("accessibility.patches.console")) local ok, err = pcall(function() speech.initialize() walltones.load() hookStates() applyPatches() end) if not ok then print("[accessibility] initialisation FAILED, game continues unmodified: " .. tostring(err)) return end accessibility.selfTest() end return accessibility