ADLIB — Authoring and Design Language for Interactive Behavior
ADLIB is a small language for
describing how game characters behave and react, plus the compiler, tools and runtime around it.
A plan is a set of behaviours (states). Each behaviour has interactors that react to
messages and collisions by running actions, consulting decisions, scheduling timers and
switching to another behaviour. The game supplies the vocabulary (the names of its messages,
actions, decisions and behaviour types) and implements the primitives in C++. Plans are data:
they are compiled to compact .PBN p-code and interpreted by the runtime.
This package is a reconstruction of the ADLIB system that Stuart Rosen and Robert Duisberg's studio Wizbang! built and used for Activision's HyperBlade (1996), recovered from the shipped game and documented so that it can be used again.
| component | what it is |
|---|---|
| libadlib | the runtime VM (behaviours, interactors, agenda, message queue, decision paths for lockstep networking), PBN reader/writer, vocabulary loader, host API; C++17, no dependencies |
| adlibc | the compiler, ADLIB source → .PBN; byte-identical to the 1996 compiler in --compat=adlib1996 mode |
| adlib (adlib-tools) | dump, decompile, graph (DOT/Mermaid), inspect/lint, compare, trace |
Compiler compatibility (vs. the original 1996 compiler, --compat=adlib1996)
───────────────────────────────────────────────────────────────────────────
32 / 32 shipped plans byte-identical
17,547 / 17,547 fuzz cases identical
185 compatibility fixtures passing
- The compiler is byte-exact. In
--compat=adlib1996mode, adlibc matches the original 1996 compiler byte for byte for accepted inputs and reproduces its diagnostics (message, line, byte offset) for rejected inputs. The default--compat=adlibpreserves the recovered language while lifting fixed implementation limits that are not part of the language itself. The evidence was produced against the original executable (HyperBlade's, 1996), run under emulation next to adlibc:- a compatibility corpus of 185 fixtures (
fixtures/compiler/) whose expectations are the original compiler's results with the package's own symbol files;ctestreplays them with no external data; - differential fuzzing: 17,547 / 17,547 compared cases identical in the final v0.1 campaign (0 mismatches; every earlier mismatch is a permanent fixture);
- all 32 plans shipped with HyperBlade rebuild byte-identically from source, and decompiled plans recompile byte-identically through both compilers.
- a compatibility corpus of 185 fixtures (
- Two modes:
--compat=adlib(default; the 1996 language with the 1996 implementation's limits lifted) and--compat=adlib1996(the 1996 compiler exactly, fixed limits included; refuses input the original would crash or hang on).--strictwarns about historical hazards without changing the output. - The runtime is an independent reconstruction of the 1996 interpreter semantics, made host-agnostic.
- The language specification (docs/language.md) contains only rules confirmed against the original compiler.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build # the test suite, no external data needed
./build/vending_machine # run the hello-world hostThe hello world is the vending machine, the example Rosen & Duisberg used to explain ADLIB in 1996. Its plans are ADLIB source (examples/vending_machine/VENDING.txt), compiled at build time against the game's own vocabulary header:
cd examples/vending_machine
../../build/adlibc --enumids VENDING.H --texts VENDTEXT.LST --strict -o VENDING.PBN VENDING.txt
../../build/adlib --symbols . graph --mermaid VENDING.PBNDECLARE_BEHAVIOR 6 id_BEH_Machine "HasCredit"
SET_MESSAGE_INTERACTOR 2 id_MSG_Select
Enable 1
DECIDE_BY_AMONG 3 id_DCF_EnoughCredit // the decision returns the branch: 0 = no
Block 2
DO_ACTION 2 id_ACF_Display id_TXT_T_NeedMore
CHANGE_TO_BEHAVIOR 1 _NO_BEH_CHANGE_
DECIDE_BY_AMONG 3 id_DCF_InStock
Block 3
DO_ACTION 2 id_ACF_Display id_TXT_T_SoldOut
DO_ACTION 1 id_ACF_ReturnChange
CHANGE_TO_BEHAVIOR 1 "Idle"
CHANGE_TO_BEHAVIOR 1 "Vending"
The graph adlib graph --mermaid produces for it (edge labels give the trigger and the decision
path; * marks a change made inside the action list):
flowchart LR
b0(["#0 Idle<br/>(Machine)"])
b1["#1 HasCredit<br/>(Machine)"]
b2["#2 Vending<br/>(Machine)<br/>timer 100 VendDone"]
b0 -->|"MSG Coin"| b1
b1 -->|"MSG Select [EnoughCredit=1, InStock=0] *<br/>MSG Refund"| b0
b1 -->|"MSG Select [EnoughCredit=1, InStock=1] *"| b2
b2 -->|"MSG VendDone"| b0
And a run with the trace sink on (./build/vending_machine --trace "obj=machine,kind=MSG|DCF|CHANGE|TIMER"):
t=1100ms customer: Select
[tick 10] obj=machine(0) plan=VENDING beh=HasCredit MSG Select data=0 from=1
[tick 10] obj=machine(0) plan=VENDING beh=HasCredit DCF EnoughCredit -> 1
[tick 10] obj=machine(0) plan=VENDING beh=HasCredit DCF InStock -> 1
[tick 10] obj=machine(0) plan=VENDING beh=HasCredit CHANGE HasCredit -> Vending
[tick 10] obj=machine(0) plan=VENDING beh=Vending MSG Initiate data=0 from=0
*clunk* a snack drops (stock now 0)
[tick 10] obj=machine(0) plan=VENDING beh=Vending TIMER set 100 VendDone -> 0
t=1100ms machine: HasCredit -> Vending
Using it from CMake:
find_package(adlib 0.1 CONFIG REQUIRED) # after `cmake --install build`
target_link_libraries(mygame PRIVATE adlib::adlib) # + adlib::tools for the trace sink
# or: add_subdirectory(adlib) and link adlib / adlib_tools- Getting started: build options, install, first plan, tests.
- Writing a plan: the language, as a tutorial on the vending machine.
- Embedding the runtime: vocabulary headers, registering primitives by name,
PlanHost, ticking, messages, decision paths for lockstep networking. - Language specification: every rule confirmed against the original compiler.
- adlibc: modes,
--strict, diagnostics, quirks policy. - adlib-tools: decompile, graph, inspect/lint, compare, trace.
- Compatibility: fixture corpus, fuzzing methodology, release criterion.
- Provenance: binary evidence, hashes, research notes.
- CHANGELOG, CONTRIBUTING, NOTICE.
Rosen and Duisberg described ADLIB in Game Developer (Aug/Sep 1996, "Bringing Life to
HyperBlade") as three parts: a language for behaviours and interactions, an authoring
environment, and a run-time system; plans compiled to "a compact p-code representation" by a
compiler integrated into the game, and networked play that sends "indices into duplicate plan
instruction streams" rather than state. HyperBlade shipped the compiler inside the game executable
and the plans as .PBN files. This reconstruction recovered the language from that compiler (by
static analysis and by running it under emulation), wrote a new compiler that matches it byte for
byte, reimplemented the runtime's semantics independently (first inside a source port of the game,
then as this host-agnostic library), and added the tooling the original authors listed as goals
(checking, graphs, tracing). See docs/provenance.md.
If you use or write about libadlib, please cite it using CITATION.cff (GitHub's "Cite this repository" button).
MIT, see LICENSE. This project is not affiliated with or endorsed by Activision or Wizbang!; see NOTICE.md. The package contains no code, data or plans from the original game.