August 22, 2026 - prerelease - stable
Neo Angband v0.26.0
Neo Angband 0.26.0
What you can download
| Platform | File |
|---|---|
| Windows (installer) | Neo Angband Setup 0.26.0.exe |
| Windows (portable, one file) | Neo Angband-0.26.0-portable.exe |
| macOS | .dmg, or the .zip if you prefer to unpack it yourself |
| Linux | .AppImage (no install), .deb, or .tar.gz |
| Self-hosting | neo-angband-web-0.26.0.zip - static files, any web server |
The portable Windows build and the AppImage need no installer: download, run, and the game keeps its saves in a folder beside itself.
These builds are not code-signed
There is no Apple Developer identity or Windows certificate behind this project yet, so your OS blocks the first launch.
Windows is one click: SmartScreen says “Windows protected your PC”, choose More info then Run anyway.
macOS is not one click, and the dialog it shows you does not contain the way through - it offers only Done and Move to Trash. Do this:
- Drag the app out of the
.dmg, then double-click it and press Done on the refusal. This step is required: it is what makes the permission below appear, and it expires about an hour later. - Open System Settings -> Privacy & Security and scroll to Security, near the bottom.
- Press Open Anyway on the line naming Neo Angband, authenticate, and launch it again.
Or, the same decision in one command:
xattr -d com.apple.quarantine "/Applications/Neo Angband.app"
The old right-click -> Open trick does NOT work: Apple removed that bypass in macOS 15 Sequoia.
On Apple Silicon take the arm64 build. The x64 one is for Intel Macs, not a fallback - Apple is withdrawing Rosetta 2, so on a current Mac it is likelier to refuse to launch than to run slowly.
If that trade is not one you want to make, build it yourself -
docs/INSTALL.md - or play in the browser, which needs no trust decision.
Your save
Saves survive an update. Every save-format change ships the conversion that reads the version before it, and a save this build cannot open is left untouched rather than replaced.
A mod-authoring release: a mod can now drive the debug/wizard command set on a
throwaway session, load and run for a single session without installing,
conjure an item or creature into the live game, install a content mod through
the same door the player’s own import uses, and draw a real HTML panel over
the game. Alongside these, an autoplayer no longer stalls at a -more- prompt
or loops forever on a locked door, a stale capability check that compared kind
rather than action is fixed, quitting a living character shows the two pauses
upstream shows, and several help screens are transcribed from lib/help
instead of a curated subset that had drifted from it.
Added
-
A mod can drive the game’s debug commands, on a session that has stopped being saved. A new capability,
debug:wizard, hands a consenting modctx.wizard: the depth jumps, the experience and gold and stat edits, acquirement, summoning, banishment, mapping, lighting and lore, plus a catalogue of every item, creature and artifact the running game has with the pack that added each one. Every command is the function the^Amenu already dispatches to, called from a mod’s screen instead of from a text prompt, so nothing about the rules is reproduced here.What makes it offerable is that it refuses to run until the session has been cut loose from its save. The character on disk keeps whatever the last save left, everything after that is discarded, and there is no way back - so a mod holding this can never write a cheated character over one somebody cares about. The autosave runs at the tail of a turn and throttles to three seconds, so what is given up is at most three seconds of turns; reloading the page returns to the character select with the character waiting as it was.
It is a separate grant from
debug:spawnand neither covers the other. Spawning happens to the character the player is actually playing and costs them that character’s score permanently; this one costs the session and leaves the save alone, which makes it the safer of the two for anything on disk. -
A mod can be loaded for one session, without joining your library. A new tier of mod source holds an archive in session storage instead of installing it: the game picks it up on the next reload, composes it exactly as it composes any other pack, and forgets it when the game is closed. It is reached two ways.
The player’s way is a new row on
Import a mod- “Try a .zip for this session only” - and it accepts an archive that ships CODE, because a player choosing a file is making the same decision they make when they import one permanently. It asks in a screen of its own first, which names the code files, shows the archive’s size and digest, and lists what the manifest asks for in the same words and the same order the install consent prompt uses. Grants made there are held beside the archive rather than written to the stored consents, so trying somebody’s mod once leaves no standing permission behind.A mod’s way is
ctx.loadModForSession(bytes), behind the newmod:sessioncapability, and it is CONTENT ONLY on exactly the termsmod:installis: code under any extension is refused, and so is an archive whose manifest asks for a capability. A mod handing the engine another mod’s code to run is a different act from a player choosing a file, and it stays refused.mod:sessionis a separate grant frommod:installrather than a relaxation of it - an install arrives switched off and waits for the player, a session load is on as soon as the game reloads - and the capability comparison checks the action so neither consent sentence can be spent on the other.Everything that makes an install safe runs here too, through the same functions: the third-party switch before the archive is opened, the archive ceilings and the zip-slip check, the standards inspection that refuses a mod which would install and then do nothing, and the origin pin, so a staged archive cannot shadow an installed mod of the same id under a different origin. A staged copy of an id you already have DOES shadow the installed one for the session, which is the point of trying a draft, and the collision is reported on that mod’s row.
What is temporary is the mod, not what it does. The archive is forgotten; a character it changed stays changed, anything it stored stays stored, and anything it sent has been sent. Every screen involved says so, because “just for this session” reads as a safety feature and is not one. The lifetime is also a strong convention rather than a boundary: session storage survives a reload, which is what makes the tier work, and a browser restoring a closed or crashed window restores it too. So a session mod is always listed, always marked
SESSION ONLY, and its detail screen offersDrop itinstead of an on/off switch - it is on because it was staged, and dropping the archive is the only thing that stops it. -
A mod can conjure an item or a creature into the live game, and the character is marked for it.
ctx.debug, behind the newdebug:spawncapability, drops one item at the player’s feet or scatters one creature near them, exactly as the debug commands do. It adds almost no ability and a good deal of honesty: every primitive behind it was already onctx.core, and the gate they check reads adebugflag out of a deps bag the caller assembles - so a mod could always have conjured whatever it liked, with no capability and no trace.What the capability adds is the mark. The first use in a character asks the game’s own debug question, in the same words and through the same function
^Auses, and accepting sets the same permanentNOSCORE.DEBUGbit - before anything is placed, so nothing can arrive in a character the player did not agree to spend. It also adds a line on the consent list, its own kind with no wildcard over it, so a player can see exactly which of their mods can do this.The question is asked on the character grid, which a mod’s own modal panel would be covering, so the first spawn in a character is refused by name while one is open rather than posing a prompt nobody can see or answer. Placement is the game’s and the API has no coordinates.
-
A mod can install a content mod.
ctx.installMod(bytes), behind the newmod:installcapability, takes the bytes of a mod archive and lands it through the same door the player’s own zip import uses - so it is read under the same ceilings, inspected against the same requirements in the same words, keyed and digested the same way, and pinned to the same origin on first import. This is the callerModProjectwas written for: it has emitted a mod folder’s exact bytes since it was written and had nothing to hand them to, because nothing a mod can reach turns bytes into an installed mod.The door installs CONTENT only. An archive that ships code, under any name and not merely
plugin.js, is refused by file; so is one whose manifest asks for any capability. That is what keeps the grant the size of its own sentence: “may add records and tweaks to your library” rather than “may write a program, install it, and have you enable something it authored”. And an install is not an enable - what arrives is switched off, the player is shown its own capability list before any of it runs, and a mod takes effect on reload - which is what stops one grant becoming every grant. The consent line says both halves rather than leaving either to be discovered.Refusals are values, never throws, because the caller is a mod that will be showing the answer to a player. The bytes are copied before anything asynchronous runs, so what was inspected is what is stored.
-
A mod can draw with real HTML instead of the character grid. A mod holding the new
ui:panel.mountcapability getsctx.ui.openPanel(spec), which mounts a panel on the page above the game and hands back a shadow root to build in. The five UI seams before it all paint character cells through the same seven methods, which is right for a compass or a carried-weight readout and wrong for a form: a field with a caret, a list with a scrollbar and a table the player sorts by clicking a column are three things every browser already has and this codebase has none of.The part a mod could not do for itself is the keyboard. The front end has one keydown registration -
window, capture phase, installed at import - and every modal handler behind it cancels the event, so a real<input>on this page received nothing and its keystrokes were read as game commands instead: typing a name walked the character across the level. The door now stands down for a keystroke whose composed path runs through the top panel before it reaches the game’s canvas, so the caret decides whose key it is, per keystroke.Escape belongs to the player and a mod cannot take it. It closes the topmost panel, decided at the door before the panel is offered the key, and focus returns to the game rather than to whatever the panel had focused; a modal panel also carries a close control the host draws outside the mod’s shadow root, because a phone has no Escape key. The suppression fails OPEN: a container that has been detached, moved out of the panel layer, or made a parent of the game’s own canvas is closed and the keyboard goes back, checked as each key arrives rather than once at mount, and a panel under another is inert. A panel the player closed puts that mod on a brief pause, so reopening cannot outrun the key they used to get out, and eight panels is the ceiling because Escape closes one at a time.
The shadow root is style hygiene rather than a sandbox and the consent text says so: a plugin runs in the page’s own realm, so a mod can reach the document with or without this grant. What the grant carries is the sentence the player reads before enabling the mod - that it can draw something which looks exactly like the game’s own screens and read what is typed into it - and a container the host owns, places, stacks and takes away. Panels come down when the mod set changes, after each plugin’s
uninstall()and before the save.
Fixed
-
An autoplayer mod stopped dead at every
-more-, and only a human could free it. A turn’s tail can raise a prompt that blocks for a keypress - two screenfuls of messages, the forced-more-a level change puts in front of the stair message, the floor-item list on a pile of two or more, a shop screen - and the autoplayer clock skipped every tick while one was up. So a mod that plays the game by itself could not go downstairs: it printed “You enter a maze of down staircases.” and waited for a key that was never coming. Theauto_moreoption cleared the pager and reached neither of the other two, and it is stored per character, so it reverted every time the autoplayer started a new one.While an autoplayer holds the keyboard, the host now answers the prompt itself: one ESCAPE through the same input door every real keystroke goes through, logged each time. That is upstream’s own mechanism in this shell’s terms - its borg installs itself as the hook
inkey()consults for every key the game reads, and answers a-more-with a space before it thinks about a move at all. Nothing is answered before there is a game to play, so character creation still belongs to the player. Seedocs/modding/BORG.md. -
CellView.trapreported a locked door as a trap, and an agent that believed it hung. The field meant “this grid holds any trap record”, and a closed door’s lock IS a trap record - so is a glyph of warding, a web and a decoy. Thedisarmcommand wants a VISIBLE PLAYER trap and refuses anything else without spending a turn, so an autoplayer that walked up to a locked door disarmed it forever with game time frozen. The field is nowsquare_isdisarmabletrap, the same predicatedisarmtests and the trap layer draws from.It also closes a leak: an undetected trap used to be readable through this field, on a view whose own rule is that a trap the player has not found is not on the screen and therefore not in the view. This is a behaviour change for any mod reading
CellView.trap; seedocs/modding/MOD_COMPATIBILITY.md. -
debug:spawnwould have carried a seconddebug:capability along with it. The grant check compared the capability’s kind and not its action, which was correct by accident whilespawnwas the only debug action. Adding a second one would have meant a player who agreed to a mod conjuring one monster had also agreed to the depth jumps, the experience grants and the acquirement. The action is now compared, as it already was formod:andui:. -
^Xpauses on the way out, and shows the score it would have scored. Saving and quitting a living character prints “Press Return (or Escape).” and waits, and then, unless that key was Escape, opens the character’s would-be Hall of Fame entry - “Killed by nobody (yet!)” - to page through before the game leaves. Both are whatclose_gamedoes onceplayinggoes false, and the port had neither: it wrote the save and went straight to the title screen, so the last thing a quitting player saw was the dungeon. Neither pause asks a question or can cancel the quit, and the preview writes nothing to the score table - a score is only ever entered at a real death.^S, which saves without quitting, is unchanged, becausesave_gamestops at the save. -
The
?command summary islib/help’s own again, keyset for keyset. It had been a curated subset written when most of upstream’s keyset was unbound here, and the port has bound all of it since: the page had drifted into saying thatSsaves the game andVshows the hall of fame (they are See abilities and Display version info, as upstream), listed apthat recites prayers, and omitted the staircase keys that the playing-guide page beside it explains. It now printscommands.txtorr_comm.txtaccording torogue_like_commands, the waydo_cmd_helpchooses betweenindex.txtandr_index.txt, and names the keyset in its title. The rows this build has nothing behind are named once, above the table, instead of being left out of it. -
The symbol legend prints
symbols.txt, two glyphs to a line as the file has them, with the two paragraphs a curated version had dropped:/identifies a symbol here and user pref files load here, so both were true all along. Flattening the file into one column had also dropped upstream’sx -row and slid “Xorn/Xaren” onto the lowercasex, so the legend named a glyph no xorn has. -
The help index offers “Available symbols”,
index.txt’s own wording, and carries upstream’s pointer to the manual - which had been sitting at the foot of the command summary, 56 rows below where a player looks for it. -
Vprints the Angband copyright notice, which is what upstream’sdo_cmd_versionputs on that screen and which this build had replaced with credits alone. -
The targeting banner uses
target_display_help’s own wording again (“rdisplays details”, “+and-cycle through places”, “ttargets selection”).
Found something that does not match Angband 4.2.6? Open an issue or come and say so in the Discord.