`JEV_TRACE` alone cannot be joined to a decision: it records a time to the
second and the answers, with no run, step or action, so nothing in it can be
traced back to a run.
Wrap the client in `RecordingClient` so the last call's questions and answers
travel with the decision row they produced, and give each process a
`SESSION_ID` so rows from two sessions in the same second stay apart. Diff the
history directory before and after a session to attribute the run records it
produced.
What this supports is arm-level analysis: this decision belongs to this session,
and the session's outcome is the run file. It does NOT say whether an individual
answer was correct -- one run result attached to one step cannot label that
step. Per-decision accuracy needs replay, expert judgement, or ground truth the
code can verify on its own (lethal, legality, affordability).
A decision that asked nothing must carry no answers, so `client.last` is
cleared before each decide(); otherwise rows silently inherit the previous
step's answers.
The documented API accepts an object or array wherever a string is accepted:
`instructions`, every Choice option description, every Score level, and the
Noul `criteria.true` / `criteria.false` entries. The client sent bare strings
everywhere and never used `criteria` on a Noul at all.
Add the builders for those shapes:
* `ask()` structured instructions: question, focus, inspect, compare
* `entry()` a description with what it covers, what it is not for, examples
* `noul_criteria()` contrastive true/false criteria
* `score()` the ordered-level primitive, documented but not implemented
* `gate_choice()` an explicit floor per Choice call site
A threshold tuned on a Noul is never reused on a Choice: the two answer different
questions and are not on a comparable scale, so each gate states its own pair
(`CHOICE_TOP_MIN`, `CHOICE_MARGIN_MIN`).
Also add `answer_record()` and the `JEV_TRACE` writer, so a decision can carry
the answers that produced it. Without a trace there are no labels for any model
decision, which blocks every question about decision quality.
Hand-captured state snapshots (menu through hand_select) used to
build the facts layer and decision handlers. Live run dumps are
gitignored, these stay for reference.
eval_batch.sh: N back-to-back sessions with per-session summaries.
ab_card_skip.sh: A/B the jev vs combined card-reward skip policy and
compare deck size and progress from the game's run history.
observe -> decide -> act loop: Timeline preflight, stuck detection,
duplicate-action suppression, deck snapshot persistence. Appends one
JSON line per decision and per rejected action to
capture/decisions.jsonl for tail -f.
Batched question API (noul/choice/score), retrying transport, and a
margin-based confidence gate. Every call appends one JSON line of
questions and parsed answers to $JEV_TRACE when set; tracing never
raises, so it cannot break a run.
Pure computation: lethal damage lines, block values, threat model,
deck counts, affordability. Jev never does arithmetic; facts.py
computes the numbers and passes conclusions in.
sts2.py: thin client for the mod's localhost HTTP API, maps
state_type -> legal actions (the action space), strict
observe -> act once -> observe-again loop.
capture.py: manual state snapshot tool for building facts.py.
Full C# source of the kunology STS2MCP fork (McpMod HTTP bridge for
game state and actions) plus the prebuilt 194,560-byte release DLL and
its manifest. The rebuilt fork DLL from docs/research/03 is not saved;
rebuild from this source if the pinned 0.4.0 breaks on a game update.
DESIGN.md covers the three-layer architecture (facts in code, Jev for
tactics, gated escalation for macro). research/ documents the engine and
mod surface, the Jev classifier's measured behavior, the STS2MCP HTTP
interface, state shapes, failure modes, decision architecture, and a
run log of the first four sessions.