npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

amos-ts

v0.8.2

Published

AMOS Professional interpreter and runtime for the web

Readme

amos-ts

A TypeScript reimplementation of the AMOS Professional interpreter and runtime, so old AMOS games can run on the web.

Reference source: AMOS-Professional-Official (68000 assembly, MIT licence). The strategy is not to translate the assembly, but to reimplement the language and runtime from it:

  • .AMOS files are tokenized programs plus resource banks — we load and interpret the token stream directly.
  • The token table in +Lib.s is the authoritative instruction inventory.
  • The Amiga hardware layer (+W.s) is replaced with Canvas/WebAudio.

Run it now at amos.bitplane.net — drop a .AMOS file in and it plays. Every release is also pinned at amos.bitplane.net/v/<version>/, so a page can embed one build and keep it.

Install

npm install amos-ts
import { Runtime, TokenTable, CORE_TOKENS, tokenize, defaultExtensionTables } from 'amos-ts'

const table = new TokenTable(CORE_TOKENS)
const exts = defaultExtensionTables()   // the stock extension slots

let out = ''
const rt = new Runtime(tokenize('Print "Hello" : Print 42', table, exts), table, {
  extensions: exts,
  onText: (t) => (out += t),
})
rt.runHeadless(1000)
console.log(out)   // "Hello\n 42\n" — the space before 42 is AMOS's, not a
                   // typo: it writes one before every non-negative number

runHeadless(n) runs up to n steps and returns a status: ended, blocked (waiting on input, a Wait, or a resource still loading) or running if it hit the step cap. Nothing in the runtime blocks the thread — a driver calls it once per frame at 50 Hz, which is what the browser player does.

To load a real program, parseAmosFile gives you its token stream and banks.

Layout

src/
  loader/    .AMOS / .Abk / IFF parsing (BinReader, bank formats)
  tokens/    token table, detokenizer (listings from tokenized programs)
  interp/    the interpreter: values, variables, control flow, instructions
  runtime/   the "virtual Amiga": screens, bobs, sprites, AMAL, audio, input
  amiga/     the modelled machine and OS beneath it — Paula, the blitter,
             graphics.library, dos.library, the ProTracker replay, the VFS
  ext/       the extension registry: identities, token tables, citations
  coverage/  what is implemented and how well it is known (status.ts)
  cli/       node CLI tools (list/unpack/inspect AMOS files)
  web/       browser runner
fixtures/    gitignored — real .AMOS programs and .Abk banks for testing
docs/
  extensions/  the extension slot model, identification and evidence tiers
  amos3d/      the AMOS 3D file formats and engine, recovered from the binary

Two generated files sit at the top level: KEYWORDS.md, the per-keyword coverage manifest (npx tsx src/cli/genmanifest.ts), and UNIMPLEMENTED.md, the narrative gap list — including the honesty list of everywhere the port knowingly differs from the original, split into what can still be closed and what cannot.

Commands

npm test           # vitest
npm run typecheck  # tsc --noEmit
npm run lint       # oxlint — correctness and suspicious rules only
npm run build      # vite lib build to dist/
npm run cli -- src/cli/<tool>.ts <args>   # run a CLI tool via tsx

CLI tools in src/cli/:

| tool | what it does | |---|---| | amoslist.ts | detokenized listing plus banks | | amosrun.ts | run a program headless (.AMOS or a plain-text listing) | | amoscat.ts | detokenize to stdout — usable as an rg --pre preprocessor to grep AMOS source | | runreport.ts | the interpreter coverage census, and the regression oracle | | scan.ts | corpus parse census | | genmanifest.ts | regenerate KEYWORDS.md from src/coverage/status.ts | | genextdoc.ts | regenerate the registry table in docs/extensions/README.md | | gentable.ts, genext.ts | regenerate token tables from the original libraries | | extscan.ts | which extension each slot in a collection of programs held | | libscan.ts | what each .Lib in a collection contains (--gap vs the registry) | | libdemand.ts | rank extensions by how many programs identify to them | | extdis.ts | resolve an extension keyword to its 68k routine and disassemble it | | tddis.ts | AMOS 3D: resolve a keyword to its engine routine and disassemble it | | muidis.ts | MUI: resolve a class's method to its routine in muimaster.library and disassemble it (--tree for the class tree) | | extaudit.ts | which of an extension's implemented keywords have been read against its binary | | citecheck.ts | every routine N ($ADDR) citation still names the code it claims to | | contested.ts | keyword names two ported products both claim, and who answers | | libcat.ts | catalogue a directory of .Lib files by identity | | libpool.ts | pool several collections and report what is new | | adfx.ts | read an Amiga floppy image | | nodefs.ts, walk.ts | the node filesystem and corpus-walking helpers the others share |

The gen* tools regenerate checked-in data from the original material, and are listed separately because running one is a deliberate act. They group by what they read, which is what decides whether they can be chained:

  • npm run gentables — everything whose input is the AMOS Professional source tree or fixtures/: gentable.ts, genext.ts, genedmsg.ts, genmouse.ts, genamoscalls.ts, genpiconfig.ts, genptrig.ts. Each takes the tree's path as its first argument and defaults to ../AMOS-Professional-Official.
  • npm run gendocsgenmanifest.ts and genextdoc.ts, which read the committed tables rather than the libraries, so they work without fixtures/.
  • genfont.ts, genjdcrypt.ts and genlocale.ts are one-off imports from material that is neither: a PSF console font, JD's own unpacked source, and an AROS checkout. Run each by hand with its path when that source changes.

Disassembly tools need python3 with capstone.

Status

Core AMOS Professional is complete, and so is every extension the port has started. All twenty core areas in KEYWORDS.md read 100% — language, screens, drawing, menus, banks, text-io, objects, input, files, flow, memory, system, interface, AMAL, copper, palette, rainbows, windows and zones — as do fifty-three extension releases, among them AMCAF, the JD family, EasyLife, TOME, TURBO Plus, Personnal, LDos, AMOS 3D, MED, EME and P61. Nothing is half-ported: the remainder are extensions not yet begun.

3736 keywords implemented, 3628 of them faithful — verified against the 68k source, corroborated by byte-exact artifacts and by the official manual where they agree. The order matters and is the project's governing rule: the code that shipped outranks the prose about it, and documentation is evidence only where there is no binary to read. That rule applies to this file too, which is why the numbers above are the ones KEYWORDS.md last generated rather than a remembered figure. npm test says how many tests it took; a count here would be stale by the next commit.

Every extension a stock AMOS Professional installs is complete: Music (49, including Say and the mouth stream), Compact, Compiler, Requester, and IOPorts (38 — Serial, Printer and Parallel, with Printer Dump rendering a page and Serial Open reaching real hardware through Web Serial).

The third-party extensions are the bulk of it. The largest: AMCAF (280 across 1.40 and 1.50), EasyLife (156 across 1.0, 1.09 and 1.10), TURBO Plus (153 across three versions), Personnal (128 across 1.0b and 1.1), LDos (85 across 2.5 and 2.6), jd-prt (69), TOME (67), PowerBobs (65), AMOS 3D (64, the engine reverse-engineered from c3d.lib — see docs/amos3d/README.md), EME 3.0 (59) and JD (56). KEYWORDS.md has the full table; the short version is that no row sits between 0% and 100%.

Corpus census

npx tsx src/cli/runreport.ts --all runs all 513 corpus programs headless.

| | | |---|---| | run to a stop | 489 | | run with nothing skipped | 440 (90%) | | hit something unimplemented | 49 |

Read the second row, not the "ended with nothing skipped" line the tool prints. That line counts only programs that terminate, and most AMOS programs are games and demos that never do — 235 hit the step cap and 141 block waiting on input, both of which are correct behaviour, not failure.

That 90% is closer to a ceiling than it looks. Ranked by programs blocked rather than occurrences, the top gaps are dreg (30 programs), doscall (14), call and areg (4 each) — all of them n/a by policy, because this port reads 68k machine code and never executes it. No keyword work moves them.

--by-program says what the 49 are blocked on, but it counts programs per keyword rather than partitioning them, so its rows overlap and cannot be added up. Partitioned, the 49 are: 36 blocked with no extension keyword involved — overwhelmingly dreg/areg/doscall/call, the host and 68k escapes that are n/a by policy — and 13 that reach an extension, every one of which is that extension's own bundled program: Intuition 1.3b's test suite (inttest, inttest1..6, bug1, bug2, bcollin, intuiviewer, test0) and OS DevKit 1.61's os_help. No program in the corpus that someone wrote to use is blocked on an extension.

Hit counts are no guide at all here: igadget read is skipped 141,835 times across 3 programs, all of them those same self-tests looping.

Reach is not correctness. All of the above measures whether a program hits a missing keyword. It says nothing about whether the pixels are right — see UNIMPLEMENTED.md for every place the port knowingly falls short of the original, split into what can still be closed and what cannot.

Subsystems

  • Loader.AMOS containers (all signature variants seen in the wild) and banks: AmSp/AmIc sprites, AmBk memory banks (Pac.Pic., Samples, Music, Amal, Data...), IFF ILBM, Pac.Pic (ported line-by-line from UnPack_Bitmap), and the Compact packer, which re-packs every corpus picture byte for byte.
  • Tokens — token tables extracted from the compiled AMOS Pro 2.00 libraries (hunk file → AP20 header → C_Tk table). The detokenizer reproduces editor-style listings; the tokenizer goes the other way, so tests are written in AMOS source.
  • Interpreter — values, AMOS precedence and type rules, all control flow, procedures with the real scoping rules, Data/Read/Restore with computed labels, error trapping, Input/Print. Control flow is recomputed by a prescan rather than trusting inline branch links. Never blocks: Wait, Wait Key, Wait Vbl and Input set a blocked state the 50 Hz driver releases.
  • Display — done to 100%, and planar. Screens and bank images are Amiga bitplanes, with a chunky view derived from them, so Logbase pokes, bitplane extensions and a copper list aiming planes anywhere all address the real bytes rather than a translation. There is ONE renderer: the display is produced by interpreting the copper list — system-generated or a user's — walking BPLCON0/1/2/3, DDF/DIW, modulos, DMACON, the palette and the sprite pointers per scanline. Screens, drawing, palette, rainbows, menus, windows, zones, dual playfield, HAM/EHB, hardware and STOS animation, and the composited mouse pointer from the machine mouse bank.
  • Audio — done. The three players (music bank, MOD tracker, MED) and the wavetable synth, ported from +Music.s over an AudioSink, with the faithful read-and-clear Vumeter, voice stealing and reclaim, Sam Swap double-buffering and the LED filter.
  • AMAL — the animation language reimplemented from TokAMAL/Animeur, including bank programs and PLay's recorded movements.
  • AMOS 3D — the object format cracked from the binary (.3DO/.3DT/ .3DS), the transform chain, camera, visibility, zones and collision, and our own scanline rasteriser. docs/amos3d/README.md documents the formats.
  • Dialog / Interface — the resource banks, the dialog engine and the full Start_FSel file selector.
  • Browser runner (npm run dev) — load a .AMOS file and watch it run at 50 fps with keyboard, mouse and joystick. The Files panel is a file manager over the same virtual filesystem the program sees: drop in files, folders or zips, then rename, delete, make drawers, relabel volumes and drag rows between drawers.

Format notes recovered so far (verified against the corpus, and the assembly in +Lib.s/+Edit.s):

  • Token ids are byte offsets into the library token table; entries end in $FF, or $FE/$FD when an unnamed arg-count/function-form variant entry follows.
  • Operators have ids that are negative offsets from the end of the editor's operator table (= is $FFA2, + is $FFC0, ...).
  • Control flow tokens (If, Else, For, Repeat, While, Do, Data, Else If) carry a 2-byte inline branch link; On/Exit/ Exit If carry 4 bytes; Lvo() caches a 6-byte vector offset; Procedure carries size/seed/flags and its size links to End Proc.
  • @_apml_@ marks machine-code procedures: raw 68k code follows inline in the token stream, which real AMOS jsrs directly. The loader captures the block and skips to End Proc; this port never executes 68k, so calling one is an error. Reading and disassembling 68k is a different matter and is how much of the extension work was done.

Language semantics recovered from the assembly and the corpus:

  • Print/Str$ write a leading space before non-negative numbers (LongToAsc "avec signe" in +Lib.s) — which is why the corpus is full of the Str$(X)-" " idiom: string subtraction removes occurrences of the right operand from the left.
  • Int() on floats is a floor (SPFloor), not truncation; assignment to an integer variable truncates.
  • True is -1, comparisons return -1/0; / between integers is integer division.
  • Programs saved without the editor's Test pass store procedure calls and label targets as plain variable tokens — the interpreter falls back to procedure/label lookup for bare names.
  • Restore/Gosub accept computed string expressions as label names (e.g. Restore "Rn"+Mid$(Str$(N),2)).

Fixtures

fixtures/ is not committed — the AMOS libraries and the commercial extensions are not ours to redistribute. Put .AMOS/.Abk files there, e.g. the Amos-Professional-AGA-Releases corpus, or your own old games. The corpus integration test and src/cli/gentable.ts expect fixtures/official-amos (the AMOS/ release tree from AMOS-Professional-Official) and fixtures/aga-releases; extension libraries go in fixtures/extensions/<id>/.

Two notes for anyone searching the corpus. Tokenized .AMOS files are binary, so a plain grep -r will silently skip them if your grep is ugrep — pass -a, and run a positive control before believing a negative result. And src/cli/amoscat.ts detokenizes to stdout, so it works as an rg --pre preprocessor and greps AMOS source rather than token streams — write a one-line wrapper that execs it and point --pre at that. (There used to be a bin/ script for this; it ran dist/amoscat.cjs, which no build ever produced.)

Releasing

npm run release [patch|minor|major] runs the typecheck and the full suite, bumps the version, tags and pushes. That one tag fires both workflows: the library goes to npm (publish.yml) and the player to amos.bitplane.net (release.yml), at /, /v/latest/ and an immutable /v/<version>/.

CI runs on every push and pull request, but fixtures/ is not committed, so most of the suite skips there — see above. CI catches build breaks, not fidelity regressions. Those need a local run with the corpus in place, plus the census.

Licence

MIT — see LICENSE.

Speech is narrator-ts (MIT), a reimplementation of the Amiga narrator.device and translator.library. It ships a free rebuilt voice, not the Amiga's own tables, which are not redistributable — so Say speaks, but it does not sound like a real Amiga.

This repository contains no AMOS Professional code or data. The reference assembly is read from AMOS-Professional-Official and fixtures/ is gitignored for the same reason.