Neo Angband 0.26.0
Pre-releaseWhat 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:session
capability, 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 markedSESSION 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 a
debugflag 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^A
uses, 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 new
mod: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 merelyplugin.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 newui: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'suninstall()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 hookinkey()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. Thedisarm
command 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 predicate
disarmtests 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
readingCellView.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 that
Ssaves 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's
do_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 ("r
displays 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.