For agentic workers: REQUIRED SUB-SKILL: use
subagent-driven-development (recommended) or executing-plans to
execute this plan task-by-task. Steps use checkbox semantics and every task ends at a review
and commit gate.
Architecture. A Python 3.13 sidecar runs natively on Windows and exchanges strict JSON envelopes with an enabled DFHack Lua script through a single-writer atomic file spool. The Lua request poll is frame-based so it continues while the game is paused. Game advancement is a separate bounded tick timer. The bridge owns world revisions and receipts; Python owns requests, evidence, and later society logic.
Tech stack. Dwarf Fortress 53.15, DFHack 53.15-r2, DFHack Lua 5.3 APIs,
Windows Python 3.13, uv, Pydantic 2, HTTPX, standard-library SQLite/JSON/path
handling, pytest, Ruff, PowerShell, and GitHub.
§01 · Scope decision
This is deliberately the implementation plan for design Proof 1, not a speculative micro-plan for all four proofs. The actor, knowledge, scheduling, conversation, and governance plans consume the normalized bridge contract produced here. Writing those plans before observing the current 53.15 structures would bake guesses into load-bearing interfaces.
Once every exit criterion in §06 passes, write the next plan for one embodied dwarf using the committed fixtures and schemas from this repository. No design approval is reopened unless the bridge proves a required observation or legal action impossible.
§02 · Ground truth
| Fact | Evidence |
|---|---|
| Installed game | D:\SteamLibrary\steamapps\common\Dwarf Fortress;
release notes.txt identifies 53.15, dated 2026-06-25. |
| DFHack state | [TESTED 2026-07-13] Steam stable installed DFHack 53.15-r2
at D:\SteamLibrary\steamapps\common\DFHack; native
dfhack-run version verified the release. |
| Matching release | DFHack 53.15-r2 is the current stable 53.15-compatible bug-fix/API release. |
| Windows runtime | Python 3.13 is installed at
C:\Users\parzi\AppData\Local\Programs\Python\Python313\python.exe;
uv.exe is on PATH. |
| Source root | C:\Users\parzi\source\repos exists and is empty;
it can host the native-Windows working copy without UNC-current-directory failures. |
| Inference | The Mac Studio is the sole capable local host. Native Windows-to-Mac reachability is not yet established; WSL's supervised tunnel is only a fallback pattern. |
§03 · Global constraints
C:\Users\parzi\source\repos\dwarf-society and
implementation worktree at
C:\Users\parzi\source\worktrees\dwarf-society-runtime-boundary; run the
sidecar with native Windows Python, not from a WSL UNC path.dfhack.timeout(n, 'ticks', ...)
callback and always returns the game to paused state before emitting its receipt.advance tick callback is pending, the bridge is busy. Only
health may execute; observe, advance, and
claim_job reject with bridge_busy before reading or mutating the
changing world. The gate clears only after re-pausing, persistence, and response
publication.health, observe, advance, and
claim_job enter the Proof 1 capability manifest. No arbitrary command,
teleportation, or direct unit-state mutation exists.bridge_epoch is created once when the Lua module is enabled and remains
constant for every health response, snapshot, request fence, and receipt until disable,
re-enable, or process restart. It is never derived afresh per request./v1/models; Proof 1 makes no completion
request and has no metered API fallback.§04 · File map
Path in dwarf-society | Responsibility |
|---|---|
pyproject.toml, uv.lock | Python 3.13 package, runtime dependencies, test/lint commands. |
src/dwarf_society/contracts.py | Strict protocol envelopes, health/world/citizen/job/receipt models, protocol version. |
src/dwarf_society/config.py | Read and validate local
runtime.toml without embedding machine paths in committed code. |
src/dwarf_society/ipc.py | Atomic request publication, response wait, timeout, archive, and protocol validation. |
src/dwarf_society/client.py | Typed health,
observe, advance, and claim_job calls. |
src/dwarf_society/cli.py | Native Windows smoke and evidence commands; no policy. |
src/dwarf_society/report.py | Build the redacted runtime-boundary JSON and self-contained HTML reports from recorded receipts. |
dfhack/scripts/dwarf-society.lua | Enable/disable lifecycle, frame-based poll registration, request dispatch. |
dfhack/scripts/dwarf-society/protocol.lua | Spool paths, atomic claim/write, strict envelope checks, receipt ledger, world identity and revision. |
dfhack/scripts/dwarf-society/snapshot.lua | Read-only normalized fortress, citizen, personality, job, and relationship extraction. |
dfhack/scripts/dwarf-society/actions.lua | Preflight and apply the sole mutating Proof 1 capability: claim an existing legal job. |
scripts/install_bridge.ps1 | Copy bridge scripts into
dfhack-config\scripts and verify hashes; never edit game saves. |
tests/unit/ | Pure contract, config, IPC, client, and report tests. |
tests/integration/test_runtime_boundary.py | Opt-in live DFHack
contract test; skipped unless DWARF_SOCIETY_LIVE=1. |
tests/fixtures/runtime/ | Normalized seven-citizen snapshot, health response, available-job response, stale rejection, and duplicate receipt. |
runtime/ | Gitignored local config, spool pointer, clean save copy, logs, SQLite/evidence, and reports under a UTC run ID. |
§05 · Task plan
Consumes: observed 53.15 Steam install, Windows
Python 3.13, uv. Produces: backed-up saves, DFHack 53.15-r2,
disposable fixed embark, private repository, and passing package smoke/build checks.
$DfRoot = 'D:\SteamLibrary\steamapps\common\Dwarf Fortress'
$stamp = Get-Date -AsUTC -Format 'yyyyMMddTHHmmssZ'
Select-String -Path "$DfRoot\release notes.txt" -Pattern '^Release notes for 53\.15'
if (Test-Path "$DfRoot\hack") { throw 'Unexpected DFHack install already present' }
$backup = "D:\SteamLibrary\df-saves-backup-$stamp"
New-Item -ItemType Directory -Path $backup | Out-Null
robocopy "$DfRoot\save" "$backup\save" /E /COPY:DAT /DCOPY:DAT /R:1 /W:1
if ($LASTEXITCODE -gt 7) { throw "save backup failed: $LASTEXITCODE" }
Get-FileHash "$DfRoot\Dwarf Fortress.exe" -Algorithm SHA256
Expected: one 53.15 match, no existing hack,
robocopy exit 0–7, non-empty backup and executable hash.version. Record output showing DF 53.15 and DFHack 53.15-r2. Stop if either
value differs.Set-Location C:\Users\parzi\source\repos
gh repo create dkta-labs/dwarf-society --private --clone
Set-Location .\dwarf-society
uv init --package --name dwarf-society --python 3.13
Remove-Item .\README.md -ErrorAction SilentlyContinue
uv add 'pydantic>=2.11,<3' 'httpx>=0.28,<1'
uv add --dev 'pytest>=8.4,<9' 'ruff>=0.12,<1'
New-Item -ItemType Directory -Force -Path `
src\dwarf_society,tests\unit,tests\integration,tests\fixtures\runtime, `
dfhack\scripts\dwarf-society,scripts,runtime | Out-Nullagent_v0_fixture.
Immediately pause and save. Copy its save directory to
runtime\saves\agent_v0_clean; never use an existing region as the fixture..gitignore with exactly:
.venv/
__pycache__/
.pytest_cache/
.ruff_cache/
*.pyc
runtime/*
!runtime/.gitkeep
Add empty runtime\.gitkeep.pyproject.toml:
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
markers = ["live: requires a running disposable DFHack fortress"]
[tool.ruff]
target-version = "py313"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]tests/unit/test_smoke.py:
import dwarf_society
def test_package_imports() -> None:
assert dwarf_society.__package__ == "dwarf_society"name = "dwarf-society", a non-placeholder
description, and Dakota's author name. Remove the premature [project.scripts]
table; Task 7 adds the real CLI after dwarf_society.cli exists.uv run pytest, uv build, and
uv run ruff check ..
Expected: one smoke test passes; wheel and sdist build with the
dwarf_society package; Ruff exits 0.git add .gitignore .python-version pyproject.toml uv.lock runtime/.gitkeep `
src/dwarf_society/__init__.py tests/unit/test_smoke.py
git commit -m "chore: bootstrap dwarf society runtime"Consumes: package scaffold. Produces:
RequestEnvelope, ResponseEnvelope, WorldSnapshot,
CitizenSnapshot, JobSnapshot, and ActionReceipt with
Pydantic extra-field rejection.
tests/unit/test_contracts.py:
from uuid import uuid4
import pytest
from pydantic import ValidationError
from dwarf_society.contracts import PROTOCOL_VERSION, RequestEnvelope, ResponseEnvelope
def test_request_rejects_unknown_fields() -> None:
payload = {
"protocol": PROTOCOL_VERSION,
"request_id": str(uuid4()),
"kind": "health",
"payload": {},
"unexpected": True,
}
with pytest.raises(ValidationError):
RequestEnvelope.model_validate(payload)
def test_response_requires_matching_protocol() -> None:
with pytest.raises(ValidationError):
ResponseEnvelope.model_validate({
"protocol": "999",
"request_id": str(uuid4()),
"status": "ok",
"result": {},
})uv run pytest tests/unit/test_contracts.py -v.
Expected: import failure for
dwarf_society.contracts.src/dwarf_society/contracts.py. Use frozen, strict models and these
exact top-level interfaces:
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, model_validator
PROTOCOL_VERSION = "1"
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
class RequestEnvelope(StrictModel):
protocol: Literal["1"] = PROTOCOL_VERSION
request_id: UUID
kind: Literal["health", "observe", "advance", "claim_job"]
run_id: UUID | None = None
save_id: str | None = None
bridge_epoch: str | None = None
expected_revision: int | None = Field(default=None, ge=0)
command_id: UUID | None = None
payload: dict[str, object]
class ProtocolError(StrictModel):
code: Literal[
"invalid_request", "wrong_save", "wrong_epoch", "stale_revision", "bridge_busy",
"unsupported_capability", "precondition_failed", "internal_error"
]
message: str
class ResponseEnvelope(StrictModel):
protocol: Literal["1"] = PROTOCOL_VERSION
request_id: UUID
status: Literal["ok", "rejected", "error"]
save_id: str | None = None
bridge_epoch: str | None = None
observed_revision: int | None = Field(default=None, ge=0)
result: dict[str, object] | None = None
error: ProtocolError | None = None
@model_validator(mode="after")
def error_matches_status(self):
if self.status == "ok" and self.error is not None:
raise ValueError("ok response cannot carry an error")
if self.status != "ok" and self.error is None:
raise ValueError("non-ok response requires an error")
return selfPosition(x,y,z),
EnumScore(name,value), NeedState(name,deity_id,focus_level,need_level),
SkillState(name,rating,experience),
RelationshipState(kind,target_hf_id,strength),
JobSnapshot(id,type_name,worker_unit_id),
CitizenSnapshot(unit_id,histfig_id,name,profession,position,stress,
stress_category,current_job_id,traits,values,needs,skills,relationships), and
WorldSnapshot(save_id,bridge_epoch,revision,year,year_tick,paused,citizens,jobs).
All lists default to empty through Field(default_factory=list); all models
inherit StrictModel.unit_id values through a WorldSnapshot model
validator, and reject a relationship whose target_hf_id is negative.uv run pytest tests/unit/test_contracts.py -v.
Expected: all contract tests pass.git add src/dwarf_society/contracts.py tests/unit/test_contracts.py
git commit -m "feat: define strict bridge contracts"Consumes: protocol models. Produces:
SpoolClient.request(envelope, timeout_s) -> ResponseEnvelope, enabled Lua
bridge, and a live health round trip while the game is paused.
tests/unit/test_ipc.py for atomic publication,
response correlation, malformed response rejection, archive movement, and timeout.
The happy-path test uses a background thread that moves one request into
processing and writes the matching response.uv run pytest tests/unit/test_ipc.py -v.
Expected: import failure for SpoolClient.src/dwarf_society/ipc.py with this public surface and atomic write:
class BridgeTimeout(TimeoutError):
pass
class SpoolClient:
def __init__(self, root: Path) -> None:
self.root = root
self.requests = root / "requests"
self.responses = root / "responses"
self.archive = root / "archive"
for path in (self.requests, self.responses, self.archive):
path.mkdir(parents=True, exist_ok=True)
@staticmethod
def _write_atomic(path: Path, content: str) -> None:
temp = path.with_suffix(path.suffix + ".tmp")
with temp.open("w", encoding="utf-8", newline="\n") as handle:
handle.write(content)
handle.flush()
os.fsync(handle.fileno())
os.replace(temp, path)
def request(self, envelope: RequestEnvelope, timeout_s: float = 10.0) -> ResponseEnvelope:
name = f"{time.time_ns():020d}-{envelope.request_id}.json"
request_path = self.requests / name
self._write_atomic(request_path, envelope.model_dump_json())
response_path = self.responses / f"{envelope.request_id}.json"
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
if response_path.exists():
response = ResponseEnvelope.model_validate_json(response_path.read_text("utf-8"))
if response.request_id != envelope.request_id:
raise ValueError("response request_id mismatch")
os.replace(response_path, self.archive / response_path.name)
return response
time.sleep(0.025)
raise BridgeTimeout(f"bridge timed out after {timeout_s:.3f}s")dfhack/scripts/dwarf-society/protocol.lua. Its
init_spool() uses
scriptmanager.getModStatePath('dwarf-society') and
dfhack.filesystem.mkdir_recursive() for requests,
processing, responses, and archive. Its
claim_next() sorts dfhack.filesystem.listdir(requests) and
uses os.rename to move one .json request into
processing before decoding it.local function write_response(request_id, response)
local final = paths.responses .. request_id .. '.json'
local temp = final .. '.tmp'
json.encode_file(response, temp, {pretty=false})
assert(os.rename(temp, final))
enddfhack/scripts/dwarf-society.lua with enable semantics. Register
the poll with frames so paused requests still complete:
--@ enable=true
--@ module=true
local repeat_util = require('repeat-util')
local protocol = reqscript('dwarf-society/protocol')
local KEY = 'dwarf-society'
state = state or {enabled=false}
runtime = runtime or {}
local function poll()
protocol.process_one()
end
local function do_enable()
runtime.bridge_epoch =
('%d-%d-%s'):format(os.time(), dfhack.getTickCount(), tostring({}))
protocol.init_spool(runtime)
repeat_util.scheduleEvery(KEY, 5, 'frames', poll)
end
local function do_disable()
repeat_util.cancel(KEY)
end
function isEnabled() return state.enabled end
function setEnabled(enabled)
if enabled == state.enabled then return end
state.enabled = enabled
if enabled then do_enable() else do_disable() end
end
Dispatch only health in this task. Health returns DF version, DFHack version,
fortress-mode flag, paused state, dfhack.getSavePath(), current site ID,
the module-lifetime runtime.bridge_epoch, revision 0, and the four-item
capability manifest. In do_enable(), assign the epoch exactly once with
('%d-%d-%s'):format(os.time(), dfhack.getTickCount(), tostring({})) before
scheduling the frame poll; every handler reuses that value.scripts/install_bridge.ps1 to copy the four repository Lua paths
into $DfRoot\dfhack-config\scripts, then compare SHA-256 hashes source to
destination. It accepts only a validated -DfRoot parameter and refuses a
root whose release notes do not contain 53.15.enable dwarf-society, pause, and issue a health request from native
PowerShell through a temporary Python command. Expected: response status
ok, DF 53.15, DFHack 53.15-r2, paused=true, and no timeout.git add src/dwarf_society/ipc.py tests/unit/test_ipc.py dfhack scripts/install_bridge.ps1
git commit -m "feat: add paused-safe bridge transport"Consumes: live health bridge, normalized contracts.
Produces: observe response, committed normalized fixture, exact
53.15 field map for personality, work, and relationships.
tests/integration/test_runtime_boundary.py with
@pytest.mark.live and an environment gate. The first test calls
client.observe() and asserts: paused; exactly seven citizens; unique unit
and historical-figure IDs; non-empty names, traits, values, and needs; at least one
skill across the seven citizens; valid positions; every non-null current job refers to
a known job; and every relationship target that exists is a non-negative
historical-figure ID.uv run pytest tests/integration/test_runtime_boundary.py -v.
Expected: one skip explaining
DWARF_SOCIETY_LIVE=1.dfhack/scripts/dwarf-society/snapshot.lua. Iterate
dfhack.units.getCitizens(true, false). Read only these verified 53.15
sources: unit and historical-figure IDs; getReadableName;
getProfessionName; getPosition; current job ID;
getStressCategory; status.current_soul.skills; and
status.current_soul.personality.{traits,values,needs,stress}.local function enum_name(enum, value)
return enum[value] or ('UNKNOWN_' .. tostring(value))
end
local function extract_needs(personality)
local out = {}
for _, need in ipairs(personality.needs) do
table.insert(out, {
name=enum_name(df.need_type, need.id),
deity_id=need.deity_id,
focus_level=need.focus_level,
need_level=need.need_level,
})
end
return out
end
local function extract_skills(soul)
local out = {}
for _, skill in ipairs(soul.skills) do
table.insert(out, {
name=enum_name(df.job_skill, skill.id),
rating=skill.rating,
experience=skill.experience,
})
end
return out
endunit.relationship_ids indexed by
df.unit_relationship_type. Extract social attitudes from the unit's
historical figure relationships.hf_visual and
relationships.hf_historical, preserving relationship kind, target
historical-figure ID, and paired counter as strength. Do not write these structures.JobSnapshot records with ID, enum name, and current
worker ID. The bridge may traverse the current world job list read-only; it does not
create or cancel jobs.observe dispatch and validate the Python result through
WorldSnapshot.model_validate(response.result). Reject the response if one
citizen has no current soul; do not fabricate personality defaults.agent_v0_clean. Copy the
normalized response—not raw save data—to
tests/fixtures/runtime/initial_snapshot.json. Add a pure fixture test that
parses it with WorldSnapshot.uv run pytest tests/unit tests/integration/test_runtime_boundary.py -v
uv run ruff check .
Expected: pure tests pass; live test passes only with the live
gate; Ruff exits 0.git add dfhack/scripts/dwarf-society/snapshot.lua \
src/dwarf_society/contracts.py src/dwarf_society/client.py \
tests/integration/test_runtime_boundary.py tests/fixtures/runtime/initial_snapshot.json
git commit -m "feat: expose canonical citizen snapshot"Consumes: health/observe bridge. Produces:
client.advance(ticks, expected_revision), monotonic site revision, changed
bridge epoch after restart, and stale-request rejection before mutation.
rejected/stale_revision; a wrong save ID returns
rejected/wrong_save; and a wrong bridge epoch returns
rejected/wrong_epoch. Start a longer advance and, while it is pending,
assert health returns busy=true while observe and claim-job requests return
rejected/bridge_busy without revision or world changes.run_id, save_id, bridge_epoch,
expected_revision, and command_id. Enforce this with a
RequestEnvelope model validator for advance and
claim_job.dfhack.persistent.getSiteData('dwarf-society', {revision=0, receipts={}})
and saveSiteData after every successful mutating command. Save identity is
dfhack.getSavePath() .. ':' .. dfhack.world.getCurrentSite().id.advance with two distinct clocks and one bridge-wide busy gate:
local function advance(request, respond)
validate_mutation_fence(request)
if runtime.busy then
return reject(request, 'bridge_busy', 'advance already pending')
end
local ticks = tonumber(request.payload.ticks)
if not ticks or ticks < 1 or ticks > 1200 then
return reject(request, 'invalid_request', 'ticks must be 1..1200')
end
if not dfhack.world.ReadPauseState() then
return reject(request, 'precondition_failed', 'game must begin paused')
end
runtime.busy = {request_id=request.request_id, kind='advance'}
dfhack.timeout(ticks, 'ticks', function()
dfhack.world.SetPauseState(true)
state.revision = state.revision + 1
persist_state()
respond(ok_receipt(request, {ticks=ticks, busy=false}))
runtime.busy = nil
end)
dfhack.world.SetPauseState(false)
return 'deferred'
end
The outer request poll remains scheduled every five frames and may not
be replaced with a tick schedule. While runtime.busy is set, dispatch
health with the pending request ID and reject every other request with
bridge_busy before snapshot extraction or action preflight.processing until its tick callback
re-pauses the game, increments and persists revision, writes and archives the response,
and only then clears runtime.busy. On disable, map unload, or world unload,
force pause, cancel the tick callback with dfhack.timeout_active, publish an
internal_error response for the deferred request when the spool remains
available, archive it, and clear the gate. Never leave a deferred request silently
pending.git add dfhack src/dwarf_society tests
git commit -m "feat: add fenced pause-step clock"Consumes: citizen/job snapshot and revision fence.
Produces:
client.claim_job(unit_id, job_id, command_id, expected_revision), strict
preflight, one legal mutation, duplicate receipt replay, and post-action reconciliation.
observe; do not construct a raw job in Lua.dfhack/scripts/dwarf-society/actions.lua with an allowlist table
containing only claim_job. Preflight requires a current citizen returned by
getCitizens, a current job with no worker, and
dfhack.units.isJobAvailable(unit, true). The true preserves
interruptible social activity rather than stealing the dwarf from it.dfhack.job.addWorker(job, unit). Immediately read
dfhack.job.getWorker(job); success requires the requested unit ID. Then
increment revision, persist this exact receipt under command ID, and respond:
{
command_id = request.command_id,
capability = 'claim_job',
outcome = 'applied',
unit_id = unit.id,
job_id = job.id,
before_worker_unit_id = -1,
after_worker_unit_id = unit.id,
revision_before = revision_before,
revision_after = state.revision,
}duplicate=true and do not re-run preflight or mutate revision. If the
command ID is known but request payload differs, return
rejected/precondition_failed with “command ID payload mismatch.” Store a
canonical payload hash or canonical encoded payload beside each receipt.observe and assert the world snapshot
shows the requested worker on the requested job. The Python client records attempted
request, receipt, and reconciled snapshot under one causal record.agent_v0_clean after the test.git add dfhack src/dwarf_society tests
git commit -m "feat: add idempotent legal job claim"Consumes: complete bridge, fixtures, Mac oMLX ground truth. Produces: verified native-Windows private route, redacted self-contained report, focused full-suite result, and the evidence contract for the next implementation plan.
src/dwarf_society/config.py and
tests/unit/test_config.py. Parse gitignored runtime/runtime.toml
with standard tomllib. Required keys are df_root,
spool_root, omlx_base_url, and
inference_route constrained to tailscale or
supervised_ssh_tunnel. Reject URLs whose host is neither loopback nor the
recorded private Mac name/address.Invoke-RestMethod -Method Get -TimeoutSec 5 `
-Uri 'http://dakotas-mac-studio:8080/v1/models'
If this fails, open a supervised Windows OpenSSH tunnel in a dedicated terminal:
ssh.exe -NT -L 127.0.0.1:18080:127.0.0.1:8080 dakotas-mac-studio
Then verify http://127.0.0.1:18080/v1/models. Record only route kind,
status, latency, and model count—no private address or model inventory in the repo.client.check_inference_route() using an HTTPX client with 5-second
connect/read/write/pool timeouts, redirects disabled, and exactly one GET to
/v1/models. No completion method exists in Proof 1.src/dwarf_society/report.py and pure tests. The report consumes
recorded health, initial snapshot, advance receipt, stale rejection, applied claim,
duplicate receipt, reconciled snapshot, save/load receipt, and inference-route result.
It emits runtime/<run-id>/runtime-boundary.json and a self-contained HTML
rendering with PASS/FAIL for each §06 criterion.src/dwarf_society/cli.py commands
health, observe, advance,
claim-job, check-inference, and
report-runtime-boundary. Every mutating command requires an explicit
--expected-revision and prints the receipt; no command accepts arbitrary Lua
or DFHack text.uv run pytest -m "not live" -v
$env:DWARF_SOCIETY_LIVE = '1'
uv run pytest -m live tests/integration/test_runtime_boundary.py -v
uv run ruff check .
uv run python -m dwarf_society.cli report-runtime-boundary
Expected: all pure and live tests pass; Ruff exits 0; report
marks every exit criterion PASS.README.md with exact install/enable/test/report commands, DF/DFHack version
lock, disposable-save warning, frame-vs-tick invariant, capability manifest, and link
to the approved design. Do not document actor/governance features as implemented.git add README.md src/dwarf_society tests pyproject.toml uv.lock
git commit -m "docs: record runtime boundary proof"§06 · Exit gate
All conditions are mandatory. A bridge that can read but not safely mutate is not a completed runtime boundary.
| Criterion | Evidence |
|---|---|
| Exact compatibility | Recorded DF 53.15 + DFHack 53.15-r2; disposable save only. |
| Paused-safe transport | Health and observe requests complete while paused; source shows a frame-based poll. |
| Canonical citizens | Strict fixture parses exactly seven unique citizens with identity, position, work, traits, values, needs, skills, stress, and relationships. |
| Bounded clock | Advance begins and ends paused, advances the requested bounded ticks, increments revision once, exposes busy health, and rejects all non-health requests while the world is advancing. |
| Fencing | Wrong save, wrong epoch, and stale revision reject before mutation. |
| Legal action | One existing game job is assigned to one available citizen via DFHack job APIs; fresh observation confirms it. |
| Idempotency | Exact duplicate command returns the durable first receipt across save/load and does not increment revision or reapply. |
| Private inference route | Native Windows sidecar reaches Mac oMLX
/v1/models over a verified private route or supervised loopback tunnel. |
| Recoverability | Every live test begins from a clean copied save; existing personal saves remain untouched; bridge restart changes epoch and reconciles safely. |
| Evidence | Focused tests and Ruff pass; report contains no private address, model inventory, personal save name, secret, or raw game binary. |
§07 · Plan self-review