A rule-based simulation engine combining a LISP-like DSL with an archetype-based ECS.
Longtable is designed for text-based simulation games where complex emergent behavior arises from simple, declarative rules. Think "Zork meets Dwarf Fortress" — rich world simulation driven by pattern-matching rules rather than imperative scripts.
- Persistent World State — Immutable snapshots with structural sharing enable time travel, speculation, and deterministic replay
- Pattern-Matching Rules — Declarative rules fire when patterns match, with automatic refraction to prevent infinite loops
- Entity-Component-Relationship — Archetype-based ECS with first-class relationships and typed schemas
- LISP-like DSL — Homoiconic syntax with macros for domain-specific abstractions
- Derived Components — Computed values with automatic cache invalidation
- Constraint Checking — Invariants validated after each tick with rollback support
- Time Travel Debugging — Git-like branching, rollback, and world state diffing
- Observability — Tracing, breakpoints, watches, and causal "why" queries
- 125+ Native Functions — Comprehensive standard library for collections, math, strings, and more
git clone https://github.com/ndouglas/longtable.git
cd longtable
cargo build --release./target/release/longtableOr with files:
./target/release/longtable examples/adventure/_.lt;; Basic arithmetic
> (+ 1 2 3)
6
;; Collections
> (map (fn [x] (* x 2)) [1 2 3 4 5])
[2 4 6 8 10]
> (filter (fn [x] (> x 2)) [1 2 3 4 5])
[3 4 5]
> (reduce (fn [acc x] (+ acc x)) 0 [1 2 3 4 5])
15
;; String operations
> (str/upper "hello world")
"HELLO WORLD"
> (str/split "a,b,c" ",")
["a" "b" "c"]
;; Math functions
> (sin (/ pi 2))
1.0
> (sqrt 16)
4.0
;; Vector math (for simulations)
> (vec+ [1 2 3] [4 5 6])
[5.0 7.0 9.0]
> (vec-normalize [3 4])
[0.6 0.8]The examples/sudoku/ directory contains a complete constraint-propagation Sudoku solver implemented in Longtable's DSL:
./target/release/longtable examples/sudoku/_.lt> (load-puzzle (medium-1))
> (print-grid)
+-------+-------+-------+
| . . . | 2 6 . | 7 . 1 |
| 6 8 . | . 7 . | . 9 . |
| 1 9 . | . . 4 | 5 . . |
+-------+-------+-------+
| 8 2 . | 1 . . | . 4 . |
| . . 4 | 6 . 2 | 9 . . |
| . 5 . | . . 3 | . 2 8 |
+-------+-------+-------+
| . . 9 | 3 . . | . 7 4 |
| . 4 . | . 5 . | . 3 6 |
| 7 . 3 | . 1 8 | . . . |
+-------+-------+-------+
> (solve)
Naked single: R5C1 = 3
Naked single: R6C1 = 9
Naked single: R1C8 = 8
... (45 placements)
+-------+-------+-------+
| 4 3 5 | 2 6 9 | 7 8 1 |
| 6 8 2 | 5 7 1 | 4 9 3 |
| 1 9 7 | 8 3 4 | 5 6 2 |
+-------+-------+-------+
| 8 2 6 | 1 9 5 | 3 4 7 |
| 3 7 4 | 6 8 2 | 9 1 5 |
| 9 5 1 | 7 4 3 | 6 2 8 |
+-------+-------+-------+
| 5 1 9 | 3 2 6 | 8 7 4 |
| 2 4 8 | 9 5 7 | 1 3 6 |
| 7 6 3 | 4 1 8 | 2 5 9 |
+-------+-------+-------+
Puzzle solved
The solver demonstrates:
- Entity-Component architecture: 81 cells with
:position,:value,:candidatescomponents - Constraint propagation: Naked singles, hidden singles, X-Wing, Swordfish, XY-Wing
- Backtracking with state save/restore: For puzzles requiring guessing
- Declarative logic: Pure functional implementation in ~600 lines of DSL code
The examples/logic-grid/ directory contains a constraint-satisfaction solver for logic grid puzzles (the kind where you match items across categories using clues).
Puzzle credit: The included "Minnetonka Manatee Company" puzzle is from PuzzleBaron's Logic Puzzles.
./target/release/longtable examples/logic-grid/_.lt> (solve-manatee-puzzle!)
============================================================
MINNETONKA MANATEE COMPANY LOGIC PUZZLE
============================================================
Setting up logic grid...
Grid setup queued (588 cells).
Applying basic exclusion clues...
Clue 2: Sea Cow != Silver Springs
Clue 3: Rainbow Reef != Jacobson
Clue 5: Mellow Mel != 3 manatees
Clue 8: Samantha != 4, Samantha != Silver Springs
Basic clues applied. Running propagation...
=== Applying deduced constraints ===
Clue 10 -> Hollow Hole = 7 manatees, Benny II = Romero = 9
Solved: :location/:hollow-hole = :manatees/:7
Solved: :boat/:benny-ii = :captain/:romero
...
============================================================
SOLUTION
============================================================
Boat Captain Manatees Location
------------ ---------- -------- --------------
:benny-ii :romero :9 :betty-beach
:daily-ray :espinoza :6 :yellow-bend
:foxy-roxy :armstrong :4 :rainbow-reef
:mellow-mel :yang :7 :hollow-hole
:samantha :jacobson :5 :treys-tunnel
:sea-cow :quinn :8 :arnos-spit
:watery-pete :preston :3 :silver-springs
============================================================
The solver demonstrates:
- Grid cell entities: 588 cells tracking all category pairings (boat×captain, boat×location, etc.)
- Constraint propagation: Eliminating possibilities when cells are solved
- Bidirectional solving: When boat→captain is solved, captain→boat is automatically solved
- Declarative clue application:
(clue-is! :boat :benny-ii :captain :romero)to assert facts
The examples/adventure/ directory contains a text adventure game ("The Dark Cave") showcasing natural language parsing and rule-based simulation:
./target/release/longtable examples/adventure/_.lt===============================================
THE DARK CAVE
A Text Adventure Demo
===============================================
Type 'help' for a list of commands.
Welcome to the cave entrance!
> look
Cave Entrance
You stand at the mouth of a dark cave. Sunlight streams in from behind you,
illuminating rough stone walls covered in moss. A cold breeze carries the
smell of damp earth from deeper within.
You can go south.
> l lantern
A sturdy brass lantern with a glass chimney. It provides a warm, steady light.
> s
Main Hall
A vast underground chamber stretches before you. Ancient stalactites hang
from the ceiling like stone fangs. Multiple passages lead off into darkness.
You can go east, west, or north.
> e
Crystal Cavern
The walls here are studded with glowing crystals that cast an eerie blue
light. The crystals hum faintly, resonating with some unknown energy.
You can go west or south.
> l sword
An old iron sword. Despite its rust, the edge is still sharp.
The adventure demonstrates:
- Natural language parsing: Player input like "examine brass lantern" is parsed into structured commands via vocabulary and syntax patterns
- Entity-Component storage: Rooms, items, and the player are entities with components (
:name,:description,:tag/player, etc.) - Relationships: Spatial relationships (
in-room,exit/north) connect entities - Action system: Actions have parameters, preconditions, and handlers
- Queryable world state:
(query :where [[?p :tag/player true] [?p :in-room ?r] [?r :name ?n]] :return ?n)
longtable [OPTIONS] [FILES...]
OPTIONS:
-h, --help Print help information
-V, --version Print version information
-b, --batch Load files and exit (no REPL)
DEBUG OPTIONS:
--trace Enable rule tracing output
--trace-vm Enable VM instruction tracing
--trace-match Enable pattern match tracing
--max-ticks N Limit ticks before exit (for testing)
--dump-world Dump world state after loading files
EXAMPLES:
longtable Start interactive REPL
longtable world.lt Load world.lt, then start REPL
longtable -b test.lt Load test.lt and exit
longtable --trace -b sim.lt Run with rule tracing;; Basic commands
(def name value) ;; Define a session variable
(load "path") ;; Load a .lt file
(save! "path") ;; Save world state to file
(load-world! "path") ;; Load world state from file
(tick!) ;; Advance simulation by one tick
(inspect entity) ;; Inspect an entity's details
;; Explain system
(why entity :component) ;; Why does entity have this value?
(why entity :component :depth 5) ;; Multi-hop causal chain
(explain-query (query ...)) ;; Explain query execution
;; Debugging
(break :rule foo) ;; Breakpoint on rule
(break :entity ?e :component :hp) ;; Breakpoint on component access
(watch (get ?e :health)) ;; Add watch expression
(continue) ;; Resume execution
(step-rule) ;; Step to next rule
;; Tracing
(trace!) ;; Enable tracing
(trace-off!) ;; Disable tracing
(get-traces) ;; Get trace buffer
;; Time travel
(rollback! 5) ;; Go back 5 ticks
(goto-tick! 42) ;; Jump to tick 42
(branch! "experiment") ;; Create branch at current tick
(checkout! "main") ;; Switch to branch
(branches) ;; List all branches
(merge! "experiment") ;; Merge branch into current
(diff 40 42) ;; Compare two ticks
(history) ;; Show recent history
(timeline) ;; Show timeline statusKeyboard shortcuts:
Ctrl+D— Exit REPLCtrl+C— Cancel current inputTab— Autocomplete keywords
longtable_foundation — Core types, values, persistent collections
longtable_storage — Entity-component storage, relationships, world state
longtable_language — Lexer, parser, compiler, bytecode VM
longtable_engine — Rule engine, pattern matching, queries, constraints
longtable_stdlib — Standard library functions
longtable_runtime — REPL, CLI, serialization
longtable_debug — Tracing, debugging, time travel
Layer 5: longtable_debug — Tracing, debugging, time travel
Layer 4: longtable_runtime — REPL, CLI, serialization
longtable_stdlib — Standard library functions
Layer 3: longtable_engine — Rule engine, pattern matching, queries
Layer 2: longtable_language — Lexer, parser, compiler, bytecode VM
Layer 1: longtable_storage — Entity-component storage, world state
Layer 0: longtable_foundation — Core types, persistent collections
map, filter, reduce, first, rest, last, nth, count, empty?, conj, cons, concat, reverse, sort, sort-by, take, drop, take-while, drop-while, partition, group-by, flatten, distinct, dedupe, interleave, interpose, zip, zip-with, repeat, range, into, vec, set, keys, vals, get, assoc, dissoc, merge, contains?, every?, some, not-any?, not-every?, remove
+, -, *, /, mod, rem, abs, neg, inc, dec, min, max, clamp, floor, ceil, round, trunc, sqrt, cbrt, pow, exp, log, log10, log2, sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, pi, e, rand, rand-int
vec+, vec-, vec*, vec-scale, vec-dot, vec-cross, vec-length, vec-length-sq, vec-normalize, vec-distance, vec-lerp, vec-angle
str, str/len, str/upper, str/lower, str/trim, str/trim-left, str/trim-right, str/split, str/join, str/replace, str/replace-all, str/starts-with?, str/ends-with?, str/contains?, str/blank?, str/substring, format
nil?, some?, int?, float?, string?, keyword?, symbol?, bool?, number?, list?, vector?, map?, set?, coll?, fn?, entity?, type
=, !=, <, <=, >, >=, not, and, or, if, when, cond
cargo build # Build all crates
cargo test # Run all tests (~1000 tests)
cargo bench # Run benchmarks
cargo clippy --all-targets # Lint
cargo +nightly fmt --all # Format (requires nightly)
cargo doc --no-deps --open # Generate documentationRequires Rust 1.85.0 or later (Edition 2024).
Benchmark highlights (M1 Mac):
| Operation | Time | Notes |
|---|---|---|
| VM simple op | ~500 ns | 2M ops/sec |
| Function call | ~1 µs | Per call overhead |
| Pattern match | ~100-230 ns | Per pattern |
| Component get | 66 ns | O(1) lookup |
| World clone | ~50 ns | Structural sharing |
| Entity spawn | ~480 ns | With components |
See cargo bench for full benchmark suite.
This project is released into the public domain under the Unlicense.