The Ledger

September 22, 2026

Neo Angband v1.16.0

Added

  • InternalModding-API ctx.readMod(ref) resolves a mod repository reference through the same tags-API, channel-filter and manifest walk the mods screen’s own install door uses, and returns its manifest and payload listing. It never fetches file bytes and never installs anything. An address that does not resolve, or one that resolves to a version this build cannot run, comes back as a refusal rather than a throw. Present only when a mod’s manifest declares the new mod:read capability and the player has consented to it (#172).
  • VisibleUI Subwindows too small to read comfortably are now hidden instead of staying at an illegible size. A second, more generous minimum (140px, alongside the existing 96px anti-invisibility floor) decides whether a panel gets to stay: when the enabled panel set cannot all fit above that comfortable minimum in the real viewport, the smallest panel is dropped and its neighbours reflow into the freed space, repeating until everything remaining fits or only one panel is left. A one-time message names whichever panel(s) were hidden. A layout that already fits comfortably today is unaffected (#275).
  • VisibleUI One item can now be thrown away from the missing-mod set-aside, instead of only the all-or-nothing keep/purge choice. The “Set aside by a missing mod” screen gains a “throw one thing away” action that lists every quarantined entry flat, asks which to delete, then asks again with the entry’s name, the mod it came from, and the word “permanently” on the destructive row, the same house style the all-purge prompt uses. The deletion removes only that one entry from the underlying store and forces a save; the rest of the pack stays frozen, and reinstalling the mod still rehydrates the survivors (#76).
  • VisibleUI The home now has its own door into the missing-mod stash, instead of only reaching it through Mods. Standing in the home and pressing ‘k’ opens the same “Set aside by a missing mod” screen the Mods menu already shows; the key and its help-legend row appear only while something is actually stashed, and neither one adds anything to the home’s own inventory or spends a home storage slot (#76).
  • InternalModding-API Two new ModHooks events let a mod react to a monster sighting or an artifact identification without polling. monsterBecameVisible fires the live Monster at the same MFLAG_VISIBLE transition update_mon already counts into a race’s “sights” lore, once per fresh sighting and never on a turn where an already-visible monster merely stays visible. artifactIdentified fires the live GameObject alongside its already-resolved Artifact record at the same OBJ_NOTICE.ASSESSED transition objectTouch already logs to history, once per object rather than on every re-touch of an already-known artifact. Both are notifications: core does not read a return value, and every enabled mod that declares one is told, in load order (#171).
  • VisibleModding-APIUI Pasting a mod’s repository address now shows a pre-install summary before anything is installed. What the mod would add, and what it patches, replaces or removes (each touched record paired with whether its owning pack is currently enabled), its requested capabilities in the mod manager’s own plain language, its license, and any conflict it declares against an already-enabled mod (or one that mod declares against it) are all shown together, with a confirm-or-cancel choice before the existing install path runs. Recommended mods and other registries are unchanged (#23).
  • VisibleUIModding-API The visible-monster list (’[’) can now show a one-line key explaining its row colours. A new display seam, setMonsterListColorKey, adds the line naming that violet marks a unique, red marks a monster whose native level is above the current dungeon depth, and white marks an ordinary monster for the depth - the same convention the row colours already followed - to both the interactive screen and its passive subwindow. Off by default: core’s own screens are unaffected until a mod turns the seam on (#269).
  • InternalModding-API The root keydown handler now judges whether a keydown is a stale key-repeat, and hands that judgment to mods through ctx.keyRepeat. The browser’s own native auto-repeat dispatch is what actually keeps firing an action after a key is released or a fast monster closes distance, below any queue this codebase owns, so nothing here could tell a held key apart from a fresh press before now. The new judgment combines the browser’s own event.repeat flag with the gap since the previous keydown of the same key, and also reports how stale an event was by the time it was handled. Nothing yet reads the verdict to change what a repeat does; this is the read-side primitive only, progress toward #35.
  • InternalModding-API A mod can now read and write its own live per-character storage during play, instead of approximating a save key from birth-fixed facts. ctx.characterStore (get() / set(value)) is the live counterpart to migrateBag: it reads and writes the same save bag, scoped to the mod’s own id exactly as ctx.prefs is, so one mod’s storage can never collide with or overwrite another’s. A write is stamped with whatever saveSchema the mod’s manifest currently declares, the same number migrateBag reads back on a later load. Entirely ungated, and present only once there is a live character; a value that cannot round-trip through JSON is rejected rather than reaching the save (#171).
  • VisibleUIModding-API The game now notices a character file waiting in a mod’s cloud-backup folder that this machine has not played yet, and offers to bring it in. Checked once per launch: BackupFolder gains list(), a cheap peek at every .neochar file in the chosen folder (name, lineage, character name and level, without decoding its save bytes), and any lineage that is neither in the local roster nor a recorded death is offered with a plain yes/no prompt. Saying yes runs the exact same import path Shift-M already uses, so the anti-scum death-ledger refusal applies exactly as it would to a manual import; saying no, or dismissing it, leaves the file untouched to be offered again on the next launch (#24).

Downloads