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
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.