Deterministic tick-based multi-agent simulation engine. Implements the Hats Simulator (Cohen & Morrison, WSC 2004): a 2D grid world where hats (agents) move, organizations plan taskforces with meeting trees, beacons are attack targets, and an Information Broker provides noisy/paid intelligence to a defender.
The engine was originally written in Zig and has been fully ported to Rust (sim/). The Zig implementation has been removed from the repo; Rust is now the only supported engine.
- Engine (
sim/, cratehatz-engine): ported and passing — population generation, planner (meeting-tree generation with capability-trade routing), meeting execution, movement, attack detection (4-condition rule), Information Broker (17 methods, free + paid/noisy), scoring. - 338 unit tests + 1 integration test (
cargo test --manifest-path sim/Cargo.toml) — all passing. - Contract replay harness (
sim/sim-replay/) — replays 33 golden request/response fixtures against the engine. All passing. - ~93% line coverage (cargo-llvm-cov) — uploaded to Codecov on every push/PR.
- WebSocket daemon is live.
sim/src/main.rsis a full tokio-tungstenite WebSocket server onws://127.0.0.1:9876. Handles all message types (sim lifecycle, all 17 broker methods, actions, defaults) and streamssim.advanceevents in real time.cli/(Python) andtui/(Go) clients work against it.
Requires Rust (stable toolchain, rustup/cargo). No Zig toolchain is needed or used — the project migrated off Zig entirely.
# compile the engine
cargo check --manifest-path sim/Cargo.toml
# run unit tests (338 + 1 integration test)
cargo test --manifest-path sim/Cargo.toml
# compile the contract replay harness
cargo check --manifest-path sim/sim-replay/Cargo.toml
# run the contract replay harness (33 fixtures)
cargo run --manifest-path sim/sim-replay/Cargo.toml -- --checkCI (.github/workflows/contract-replay.yml) runs the same two steps (cargo test + replay --check) on ubuntu-latest via dtolnay/rust-toolchain@stable.
Code coverage (.github/workflows/coverage.yml) runs cargo-llvm-cov on every push and PR, uploading results to codecov.io/gh/baranabi/hatz (badge above).
sim/
├── Cargo.toml — crate `hatz-engine`
├── src/
│ ├── lib.rs — module root, re-exports submodules
│ ├── main.rs — WebSocket daemon (tokio-tungstenite)
│ ├── types.rs — core types (HatId, Location, EventRecord, etc.)
│ ├── router.rs — dispatch by message type → handler
│ ├── sim.rs — lifecycle: initialize, advance, end + RunState
│ ├── broker.rs — 17 IB methods (free + paid) + noise model
│ ├── noise.rs — P(correct) = 1 - e^(-λ·p) noise model
│ ├── actions.rs — analyst actions (alert_beacon, arrest_hat)
│ ├── defaults.rs — per-analyst default request scheduling
│ ├── runs.rs — in-memory run registry
│ ├── population.rs — seed-driven population generator (hats, orgs, beacons)
│ ├── planner.rs — generative meeting tree planner with capability-trade routing
│ ├── meetings.rs — meeting execution with capability trades
│ ├── movement.rs — hat movement between meetings
│ ├── attack.rs — beacon attack detection (4-condition rule)
│ ├── logging.rs — structured logging
│ └── profiling.rs — timing instrumentation
└── sim-replay/ — contract fixture replay harness (separate crate, depends on hatz-engine)
All state is deterministic from seed. Same seed + params + action sequence = identical output.
Planner (planner.rs) — Generates meeting trees for runtime-created taskforces. For each required capability not already held by a taskforce member, finds an org member that holds the capability and schedules a trade at an intermediate or root meeting.
Meetings (meetings.rs) — Executes meetings with full capability trades and participant location updates.
Attack (attack.rs) — Detects beacon attacks when a taskforce at a beacon's location holds capabilities covering the beacon's vulnerabilities. 4-condition rule:
- Meeting occurs on the beacon
- Meeting is the final planned meeting of a taskforce
- Taskforce members' capabilities match beacon vulnerabilities
- Taskforce belongs to a terrorist organization
Information Broker (broker.rs) — 17 methods split into free queries (world, beacon, org, hat metadata) and paid queries (locations, capabilities, meeting details) with configurable noise.
The engine communicates via JSON envelopes (schemas in contracts/v1/):
// Request
{
"contractVersion": "1.0.0",
"type": "sim.initialize",
"requestId": "req-01",
"payload": { "seed": 12345, "params": {
"nHats": 200,
"eventLogCap": 10000,
"planningInterval": 10,
"maxTicks": 1000,
"plannerChance": 1.0
}}
}
// Response
{
"contractVersion": "1.0.0",
"ok": true,
"requestId": "req-01",
"payload": { "runId": "run-12345-1", "startedAtTick": 0 }
}Message types:
sim.initialize/sim.advance/sim.end— lifecyclesim.initializeparams (all optional):nHats(200),eventLogCap(10000),planningInterval(10),maxTicks(1000),plannerChance(1.0)plannerChance: probability the planner activates when interval ticks align. 1.0=always, 0.1=sparse, 0=never. SetplanningInterval=1, plannerChance=0.1for spec-compatible continuous sparse planning.
broker.call— Information Broker query (17 methods)action.alert_beacon/action.arrest_hat— player actionsdefaults.*— default request scheduling
These schemas are the API source of truth; they're language-agnostic and unchanged from the original Zig implementation.
Free queries (no cost, always succeed):
ib.world_dimensions,ib.beacons,ib.all_capabilitiesib.benign_organizations,ib.terrorist_organizations(partial — not all terrorist orgs revealed, per spec)ib.known_terrorist_hats(partial — overt terrorists only; covert terrorists not included, per spec)ib.members,ib.hat_advertised_colorib.events_history,ib.clear_events_history,ib.arrested_hats
Paid queries (cost + noise via P(correct) = 1 - e^(-λ·p)):
ib.last_location,ib.capabilitiesib.meeting_times,ib.meeting_locationib.meeting_participants,ib.meeting_trades
Reported at sim.end():
- Information Cost — total IB spend
- False Arrests — failed arrest attempts
- Beacon Effectiveness — hits vs false positives per beacon per alert level
These clients speak the WebSocket contract protocol and work against the live daemon on ws://127.0.0.1:9876.
Start, stop, list, and health-check daemon instances. Also usable as a library (from hatz_mgr import DaemonManager).
cd cli && uv sync
uv run hatz_mgr.py start 9876 # start + wait for healthy
uv run hatz_mgr.py start 9877 # run multiple instances
uv run hatz_mgr.py list # all tracked daemons
uv run hatz_mgr.py logs 9876 # tail daemon logs
uv run hatz_mgr.py stop-all # kill everythingOr start the daemon directly:
cd sim && cargo run # default ws://127.0.0.1:9876
cd sim && cargo run 0.0.0.0:8080 # custom addressA REPL client with 14 commands (init, advance, end, beacons, orgs, members, loc, cap, alert, arrest, events, state, help, quit). See cli/README.md.
cd cli && uv sync
uv run cli.py # connect to 127.0.0.1:9876
uv run cli.py 127.0.0.1:9877 # custom daemon
uv run cli.py --selftest # connect, initialize, advance 1 tick, endA Bubbletea interactive TUI with commands /advance, /beacons, /loc, /caps, /color, /orgs, /members, /events, /arrest, /alert, /score, /setdefaults, /help.
cd tui && go build
./tuiA benchmark harness (garden/harness.py) that runs competing analyst strategies against the simulator over a fixed seed set, scoring on attacks prevented, IB spend, false arrests, and AI token cost. See garden/README.md. Uses hatz_mgr to manage daemon lifecycle.
None — all known issues from the port have been resolved. Contract replay passes in CI, the WebSocket daemon is live, and integration tests cover the full protocol.
- Integration tests for the WebSocket daemon
- Multi-seed fan-out / parallel tick evaluation
- SIMD-friendly agent layouts (SoA, cache-locality) toward the 500k+ agent target
- Profiling harness for large-population bottleneck analysis
- Hats Simulator Spec — language-agnostic specification, entities, rules, and protocols
- Information Broker Spec — paid/free queries, noise model, population generation
- Contract Schemas — JSON request/response schemas
- Port Plan — Zig→Rust porting strategy and rationale
- Garden README — analyst benchmark harness