Commit Graph
54 Commits
Author SHA1 Message Date
John SmithandClaude d62cc5dade feat(core+engine): two-weapon fighting and Help action (iteration 74)
Completes the SRD standard-action suite. Off-hand attack reuses the
normal hit math with only the damage expression swapped, positive
ability mod withheld per SRD (negative kept), Light property modeled
explicitly (opt-in on stat blocks; undeclared weapons refused, never
silently permitted), and a new spend_bonus_action enforcement point —
plus SRD ordering (Attack action must already be spent). Help stores a
consumable advantage promise ON THE BENEFICIARY, consumed exactly once
by same-side attackers through both attack routes, cleared on refresh,
and rewound with Action refund. Ledger + gateway proxies + client
helpers throughout; seed never client-forwardable. 18 red-first tests;
228 workspace / 476 pytest green, clippy -D clean.

Co-Authored-By: Claude <[email protected]>
2026-08-24 12:30:47 -04:00
John SmithandClaude 511c6271a4 feat+fix: Ready action end-to-end; deploy crash + clamp disclosure (iterations 71-72)
Two workstreams sharing server.py, landed together:

Ready action (71): ReadiedAction on EntityState with Action spend via
the single enforcement point, READY_ACTION_SET ledger event, cleared on
round refresh and rewind; engine route + gateway proxy + CharacterSheet
"Holding:" display with description input. GM adjudication by design —
no automatic trigger matching. 6 red-first tests across layers.

Fourth-audit remediation (72): MED-HIGH deploy crash — Roll20 imports
persisting speed:None crashed float(None) → 500 on every deploy of such
characters; guarded fallback to 30 with math.isfinite checks and the WS
movement budget consuming the same value. LOW: out-of-range import
levels now disclose their clamp instead of silently snapping. 8
red-first tests.

Full suite: 470 passed / 22 skipped; clippy -D warnings clean.

Co-Authored-By: Claude <[email protected]>
2026-08-24 08:55:52 -04:00
John SmithandClaude 133d15b1ad feat: combat maneuvers reachable end-to-end (iteration 71)
All six engine action routes (grapple/shove/dodge/dash/disengage/
stabilize) gain gateway proxies with strict extra=forbid bodies and
identity forwarding — notably the deterministic seed is deliberately
NOT forwardable, so clients can't pin their own rolls. CharacterSheet
gains a Combat Maneuvers panel: target picker from the projected
roster, skill/effect toggles, self-buttons, downed-ally stabilize list
with an honest hint when player-role projection hides ally HP; results
quote engine fields and rejection codes verbatim with zero optimistic
mutation. 10 new proxy tests; suite at 459.

Co-Authored-By: Claude <[email protected]>
2026-08-24 08:32:15 -04:00
John SmithandClaude de7d011175 fix(llm+sla): empty-stream honesty; SLA rows disclose what they measure (iteration 70)
Three small audit notes closed red-first:
- Empty/[DONE]-only SSE reassembly no longer masquerades as a
  completed turn: complete_with_tools raises LLM_UPSTREAM_EMPTY and the
  tool agent surfaces that status instead of COMPLETED with blank
  narration.
- The SLA 'intent_parsing' row is relabelled intent_parsing_keyword
  (it measures regex classification); an LLM-classification row appears
  only under RUN_LIVE_LLM=1 with a real key, counting only genuine llm
  outcomes; SSE first-token requires >=3 samples or WITHHELD.
- Report ends with explicit FAILED/PASSED/WITHHELD tallies and an
  all-withheld run exits nonzero — withheld can never read green.
21 red-first tests; suite at 451.

Co-Authored-By: Claude <[email protected]>
2026-08-24 08:25:37 -04:00
John SmithandClaude 682228cc68 fix(import): Roll20 honesty seam between importer and gateway (iteration 67)
Third-audit web finding: two individually-clean modules broke at the
handoff — unparsable speed strings passed through unwarned and the
gateway fabricated speed=30; missing class/race/background/alignment
silently became "fighter/Human/Soldier" persisted as fact under a
docstring claiming every substitution was pre-warned. Now: unparsable
speed warns and persists None (never invented gameplay data); identity
fields persist neutral-empty with per-field warnings naming exactly
what was absent; missing level defaults to 1 WITH a warning; the
missing-core dedup no longer lets garbage-value warnings suppress
missing-stat notices. 7 red-first tests.

Co-Authored-By: Claude <[email protected]>
2026-08-24 07:58:16 -04:00
John SmithandClaude c3b3f78535 fix: third-audit remediation — gate integrity, wizard export, clipboard honesty (iteration 65)
- P0: pytest was RED in any clean environment — dead skip guards fired
  only after ConnectError had already escaped. Both helpers now skip
  properly when the engine is down; suite green with no live engine.
- HIGH: starter-adventure export required ?token= while the wizard sent
  an Authorization header — every click was a guaranteed 422 under a
  feature claiming to work. Route migrated to _require_auth
  (header-first); tests pin header-auth success and 401 semantics.
- MED: clipboard 'Copied!' claimed success unconditionally; now awaits
  the write and shows an honest Ctrl+C fallback on rejection or missing
  Clipboard API.

Co-Authored-By: Claude <[email protected]>
2026-08-24 07:50:36 -04:00
John SmithandClaude 1409e3b2de feat(playtest): SLA measurement harness — honest latency tables (iteration 62)
GOALS.md's latency targets were removed from the docs as unverifiable;
now they're measured. measure_slas.sh drives 200 real network calls
per category against live services and prints p50/p95/max vs targets
with a documented +5ms transport allowance: rules 0.39ms p50 (≤10ms),
spatial LoS/path 0.35/0.41ms (≤15ms), intent parsing 1.10ms (≤150ms),
SSE first-token 1.86ms (≤1200ms) — all PASS with wide margin.
Unreachable services WITHHOLD rows rather than simulating; rate-limit
429s surface as visible exclusions; sub-30-sample categories withhold
rather than judge rump samples.

Co-Authored-By: Claude <[email protected]>
2026-08-24 07:09:21 -04:00
John SmithandClaude bfe327380c fix(llm): SSE-tolerant non-streaming calls for streaming-only proxies (iteration 56)
The live suite proved llm.heretek.one answers text/event-stream even
to non-streaming requests, silently breaking complete_json /
generate_narrative / complete_with_tools via resp.json() failures. All
three now detect SSE content-type and reassemble assistant messages
from delta frames (tool_calls merged by index), feeding the unchanged
JSON extraction; JSONL records carry an honest transport marker;
malformed/empty streams degrade without guessing. Narrative blank-
stream degrades with degraded:true rather than empty-but-healthy.
11 red-first tests; suite at 436.

Co-Authored-By: Claude <[email protected]>
2026-08-24 06:27:49 -04:00
John SmithandClaude 30850a5e59 test(llm): opt-in live-LLM suite that never runs in CI (iteration 55)
pytest 'live' marker + conftest auto-skip unless LLM_KEY is set AND
RUN_LIVE_LLM=1 — structurally CI-proof. Six tests exercise the REAL
endpoint: mechanical/safety classification provenance, Concordia
generator-marker-vs-reality (a template marker while the endpoint is
alive FAILS), strict-JSON parsing, soft 60s latency budget, and a gate
proving non-mock configuration. Connection failures degrade to SKIP.

First live run surfaced two real integration bugs, reported honestly
as skips rather than fabricated passes: .env named a nonexistent model
that hangs instead of 404ing (fixed in .env), and the endpoint returns
text/event-stream even for non-streaming calls which breaks resp.json()
(fix iteration pending). Default suite unchanged: 406 passed / 8 skips.

Co-Authored-By: Claude <[email protected]>
2026-08-24 06:24:12 -04:00
John SmithandClaude fdca883144 feat(simulation): autonomous AI companion PCs with tactical roles (iteration 54)
GOALS.md Pillar 1's drop-in AI party members: tank / skirmisher /
healer roles execute coherent turns through the same authenticated
proxy path as campaign_sim (no second HTTP stack), with optional LLM
targeting refinement validated against known entities and per-turn
provenance accounting (decision_source, fallback_reason, degraded).
Honest realizability matrix: ally-heal triage from a plain player seat
is REFUSED rather than fabricated because the role projection redacts
other PCs' HP — the companion falls back to tank behavior instead.
19 red-first tests; suite at 425.

Co-Authored-By: Claude <[email protected]>
2026-08-24 06:22:25 -04:00
John SmithandClaude db0f8a137e feat(gateway): Roll20 import endpoint with owned persistence (iteration 51)
POST /api/v1/import/roll20 accepts single or campaign exports (2 MiB
bound), maps through the importer onto the existing character storage
path so imports are owned by the caller and owner-scoped everywhere;
importer ValueErrors map to 422 with the fail-loud reason, all-
malformed campaigns refuse rather than fake a 200-with-zero. Foundry
preview route is a deliberate documented 501 — directory trees need
multipart support, pinned by test so it can't regress into fake
success. 7 red-first tests; suite at 406.

Co-Authored-By: Claude <[email protected]>
2026-08-24 06:07:20 -04:00
John SmithandClaude 24ead361b2 feat(compendium): Roll20 character importer (iteration 50)
Pillar 10's second interop path, grounded in researched sources
(Roll20 API object docs, real exporter payload, 5e OGL sheet attribute
index): single-character and campaign-envelope imports, explicit-mod
preference with disagreement warnings, score-from-lone-mod derivation
always warned, honest None+warning for missing core stats, unmapped
attribute accounting, first-wins duplicates, fail-loud ValueErrors,
deterministic sorted output. 26 red-first tests; suite at 399.

Co-Authored-By: Claude <[email protected]>
2026-08-24 05:54:59 -04:00
John SmithandClaude fb42d4367b fix(gateway): bounded rate windows, capped NPC interaction magnitude (iteration 48)
Two minor audit notes closed: the gateway's sliding-window store now
sweeps stale keys past a 100k cap (mirroring the Rust twin's guard,
same sweep-on-overflow shape, injectable for tests), and NPC
interaction magnitude is bounded to (0,10] matching the disposition
engine's band-saturation scale so one call can no longer pin a stance
to an extreme. 5 new tests; suite at 373.

Co-Authored-By: Claude <[email protected]>
2026-08-24 05:50:02 -04:00
John SmithandClaude 7f592fe7a8 fix(gateway): audit remediation — session-state auth, ledger redaction, IDOR (iteration 44)
Second independent audit findings, fixed red-first:
- HIGH: /engine/session-state required no token and returned FULL
  engine state verbatim via a service-principal fallback. The route
  now requires valid auth (401 otherwise) and no code path returns
  anonymous full state.
- MED-HIGH: ledger events bypassed role projection, leaking HP/damage
  numbers for hidden entities while replay export redacted them. Both
  now share one projection policy (fail-closed summaries for
  spectators/unknown roles).
- MED: GET /characters/{id} had an IDOR — any authenticated user could
  read any character; now owner-scoped with 404 for foreign/garbage ids
  (no existence oracle).
Six remediation tests verified failing before each fix.

Co-Authored-By: Claude <[email protected]>
2026-08-24 05:32:46 -04:00
John SmithandClaude 6db24190e1 feat(compendium): Foundry module importer; tracker through iteration 41
Pillar 10 interop grounded in live schema research (foundryvtt.com
manifest docs, foundry-vtt-types, NDJSON export format): module.json +
packs parsing onto AetherTable canon via explicit per-entry mapping
tables, unmapped fields accounted per entry rather than dropped or
guessed, LevelDB-format pack dirs detected and skipped, fail-loud
ValueErrors on manifest problems, deterministic sorted output.
Roll20 guarded out of scope pending its own fixture corpus. 25
red-first tests; suite at 363.

Co-Authored-By: Claude <[email protected]>
2026-08-24 05:01:41 -04:00
John SmithandClaude 35de8764a9 feat(gateway): Concordia NPC dialogue endpoint with starter personas (iteration 38)
Pillar 5 sub-agents become reachable: GET /api/v1/npc/ lists public
persona metadata; POST /npc/{id}/respond returns norms-gated
{reply, generator, stance} (taboo-violating LLM text degrades to the
template fallback with an honest norm_rejected marker); POST
/npc/{id}/interactions shifts persistent stances through the shared
disposition engine. Three personas bound to starter-adventure lore
(Drowned Steward, Baron Vane, Cabal Emissary) with distinct goals,
norms, and voices on a deterministic shared clock. 13 red-first tests;
suite at 338.

Co-Authored-By: Claude <[email protected]>
2026-08-24 04:49:44 -04:00
John SmithandClaude e65dee3e28 feat: combat lifecycle + role-projected session state (iterations 36-37)
Two features sharing server.py, landed together:

Initiative order end-to-end (5.6): vtt-core begin/end_combat roll
d20+DEX initiative with deterministic seeded order (total desc, DEX
desc, name asc), COMBAT_BEGAN/ENDED ledger events; engine routes
/sessions/{id}/combat/{begin,end} with RBAC; gateway proxies forward
caller identity; the HUD renders the real rolled order with an honest
"Combat not started" empty state and GM controls. 7 core unit tests +
4 integration + mocked proxy tests.

Role-projected session state (spectator follow-up): session-state and
session-replay now project by caller role — GM/admin see everything,
players see own entities in full plus others as public board-token
projections, spectators get visible-only projections with HP/damage
amounts stripped from replay summaries; service-principal reads stay
verbatim; unknown roles fail closed to spectator view. is_player/
is_dead kept in projections because campaign_sim's targeting depends
on them through this endpoint. 20 red-first tests.

Suite at 325 passed.

Co-Authored-By: Claude <[email protected]>
2026-08-24 04:42:39 -04:00
John SmithandClaude 6d6e914f23 feat(agents): Concordia entity-component NPC sub-agents (iteration 34)
GOALS.md Pillar 5's dedicated NPC sub-agents: explicit Memory (bounded
episodic, relevance+recency recall), Goals (priority/satisfaction/
feasibility walk with survival fallback), Social Norms (taboo +
obligation rules), and Linguistic Style components composing a persona.
respond_to() builds structured prompts from all four components plus
live disposition stance; LLM replies that violate norms are discarded
entirely for the deterministic stance/style-shaped fallback — violating
text never reaches output. Deterministic without an LLM; honest
generator markers. 17 red-first tests; suite at 296.

Co-Authored-By: Claude <[email protected]>
2026-08-24 03:52:00 -04:00
John SmithandClaude b9539dc8c3 feat(lore): Qdrant-backed compendium RAG with honest provenance (iteration 32)
Multi-tenant retrieval per GOALS.md Pillar 7: compendium spells/
monsters/magic items indexed into the qdrant_rules collection named
and dimensioned by the repo's own collections config, payload-filter
namespacing (category + system_id + tags), deterministic UUID5 point
ids, keyword indexes on filter fields — all over raw httpx REST so the
transport stays trivially mockable. Embedding is a documented
lexical-hash embedder (MD5-bucketed, L2-normalized), not semantic;
real models are a single-function swap. lore-lookup gains semantic=true
with provenance retrieval: qdrant | substring | substring_fallback,
zero network when disabled, one honest warning when unreachable,
read-path degradation only (canon-write strictness unchanged).
29 red-first tests; suite at 279.

Co-Authored-By: Claude <[email protected]>
2026-08-24 03:41:46 -04:00
John SmithandClaude 2517c0991e feat(compendium): Sunken Crypt of Karas starter adventure (iteration 30)
GOALS.md Pillar 2's first out-of-the-box module: three encounters
referencing ONLY real srd_5_2_monsters stat blocks (unknown ids raise
at definition time, so invented stats cannot ship), deterministic
seeded layout resolved via the engine's live WFC with a documented
in-module fallback and provenance recorded in the artifact, crypt-theme
loot name references, lore seeds reusing exact canon node ids, X-card-
safe content note. Ships as a real .vttbundle through the packager
(new backward-compatible adventure.json member) behind GET/POST
/api/v1/adventures/starter routes. 12 red-first tests; suite at 250.

Co-Authored-By: Claude <[email protected]>
2026-08-24 03:19:53 -04:00
John SmithandClaude 2e973769c4 feat(gateway+client): header-based auth migration; Neo4j epistemic graph (iteration 28)
Two features sharing server.py, landed together:

Auth headers: new client auth_headers.ts migrates lobby/character/
campaign REST calls to Authorization Bearer headers (five POSTs
previously sent NO credential at all); gateway gains a _require_auth
dependency reading header-first with ?token= fallback on migrated
routes (missing token is now 401, not 422). WS query tokens unchanged.
7 new tests.

Neo4j lore: Neo4jEpistemicGraph speaks to the dockerized neo4j:5 HTTP
transactional endpoint over httpx (zero new deps), same public surface
as the in-memory manager, all three paradox checks in one tx round
trip, tier weights matching canon, startup-only selection with honest
in-memory fallback logging, request-time failures raise rather than
silently splitting canon, predicates validated UPPER_SNAKE_CASE before
Cypher assembly. 24 tests incl. in-memory parity cases.

Co-Authored-By: Claude <[email protected]>
2026-08-24 03:03:33 -04:00
John SmithandClaude 9a75612782 feat(gateway): session replay export as portable JSON (iteration 26)
GET /api/v1/engine/session-replay projects the live engine ledger into
a downloadable artifact: events reduced to {sequence_id, actor_id,
event_type, is_reverted, summary} with human-readable summaries derived
only from fields actually present — partial payloads render what
exists, unknown event types pass through verbatim, reverted events are
flagged not dropped. Authenticated via forwarded caller identity;
Content-Disposition attachment naming falls back to -unknown.json when
no combat round exists. 15 red-first tests.

Co-Authored-By: Claude <[email protected]>
2026-08-24 02:45:37 -04:00
John SmithandClaude b4baa54546 feat(simulation): social-state telemetry in campaign simulation (iteration 24)
NpcDispositionEngine wired into CampaignSimulation: accepted attacks
record 'attacked' on the target's disposition toward the attacker
(rejected actions and non-attacks record nothing), decision prompts and
scripted-policy inputs receive each visible target's current stance
toward the acting player, and reports carry per-player end-of-run
stances plus interaction totals. All disposition timestamps use an
explicit sim clock — deterministic across identical runs; the engine's
clock is pinned so wall-clock can never fire. 'aided' hook deliberately
absent (no heal action reachable through proxies) and documented.
8 new tests; suite at 192 passed.

Co-Authored-By: Claude <[email protected]>
2026-08-24 02:36:38 -04:00
John SmithandClaude 53c42b4397 fix(gateway): LLM error logs name the exception; tracker through iteration 22
httpx transport exceptions stringify to "" so the JSONL audit log
recorded status:null + error:"" — indistinguishable from nothing.
Error fields now always carry the exception type. Recorded evidence
from the first LIVE campaign-simulation run against the configured
custom endpoint (llm.heretek.one): endpoint currently unreachable
from this network; the harness degraded per-turn exactly as designed,
counted every attempt, and the engine accepted 100% of executed
actions.

Co-Authored-By: Claude <[email protected]>
2026-08-24 02:25:26 -04:00
John SmithandClaude b4913c67ac feat(simulation): agent-driven campaign simulation harness (iteration 21)
The deferred flagship: synthetic players authenticate, host/join/launch
a lobby, deploy RBAC-owned characters, and take turns through the same
authenticated engine proxies real clients use. Decisions come from the
configured custom LLM endpoint via complete_json with strict decision
validation (unknown actions / malformed JSON / bad coordinates fall
back per-turn while still counting the attempt); no key means honest
scripted mode with zero network. Every LLM call flows through the
existing gateway so logs/llm_calls.jsonl captures it. Reports carry
only counted metrics with enforced invariants (accepted+rejected ==
attempted). 13 tests, fully mocked; __main__ entry runs live from .env.

Co-Authored-By: Claude <[email protected]>
2026-08-24 02:17:38 -04:00
John SmithandClaude 39609aede7 feat(gateway): wire LLM intent classification into the turn flow (iteration 19)
Audit follow-up: classify_with_llm existed only as tested library.
execute_orchestrator_turn now classifies FIRST — keyword safety hits
short-circuit before any network call or grounding; the LLM path
engages only when VTT_LLM_CLASSIFIER != 0 and a key is configured,
falling back to keywords with provenance. Non-mechanical intents are
gated away from the engine cycle (SKIPPED_NON_MECHANICAL / SAFETY_
INTERVENTION statuses), matching the playtest harness semantics.
Responses gain classifier/classified_intent/fallback_reason fields;
unconfigured environments see zero behavior change. 8 new tests with
network blocked by an exploding httpx fake.

Co-Authored-By: Claude <[email protected]>
2026-08-24 02:02:43 -04:00
John SmithandClaude 0a182da6c1 feat(engine): post-rewind snapshot in X-card response + session-state read proxy (iteration 15)
The browser had no readable path to authoritative post-rewind state,
so local tokens drifted until next hydration. The x-card response now
carries a full serialized GameSession snapshot taken after the rewind
(Null-degrading on serialization failure), and the gateway gains POST
/api/v1/engine/session-state — the first browser-readable proxy to
authoritative engine state, with identity forwarding when a token is
present. Per-role visibility filtering documented as future work.
8 pytest proxy tests + 1 integration test pinning post-heal rewind HP.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:50:51 -04:00
John SmithandClaude 50fe903023 feat(simulation): genuine GOAP planner for faction simulation (iteration 13)
Replaces greedy first-fit with STRIPS-style forward search: actions
declare preconditions and effects (thresholds + delta wrappers), the
planner runs uniform-cost A* over world-state snapshots with
deterministic tie-breaking, dominance dedup, depth/expansion caps
bounding EspionageRaid's positive resource loop. Risk folds into
planning cost; execution stays deterministic. Legacy tick behavior is
preserved verbatim when no goal is set — same three actions, same log
lines — so existing callers are untouched. New API: set_goal/clear_
goal/plan/execute_plan/goal_progress_pending. 19 new tests including
optimality-over-greedy and legacy-equivalence pins.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:42:18 -04:00
John SmithandClaude 4527774ccb feat(gateway): heal + rest engine proxies with identity forwarding (iteration 12)
POST /api/v1/engine/heal and /api/v1/engine/rest follow the established
proxy pattern: required token Query param, _caller_actor forwarded so
engine RBAC authorizes the real caller, strict Pydantic models
(extra=forbid, Literal rest kind) mirroring the engine's
deny_unknown_fields contract so no client math crosses the boundary.
8 new proxy-identity tests; suite at 150 passed.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:41:35 -04:00
John SmithandClaude 7c755caf4d feat(routing): optional LLM-assisted intent classification (iteration 10)
classify_with_llm() prompts the configured custom endpoint
(LLM_API/LLM_KEY/LLM_MODEL) for JSON classification into the real
IntentType enum, with layered honesty and safety:
- SAFETY_INTERVENTION always wins — keyword safety short-circuits
  before any network call; an LLM safety label overrides keyword
  disagreement
- any failure (kill-switch VTT_LLM_CLASSIFIER=0, no key, malformed or
  unknown output) falls back to the deterministic keyword classifier
  with provenance fields; never raises
- RULE_OF_COOL has no enum counterpart and is deliberately not mapped
complete_json() adds fence-tolerant JSON extraction + non-streaming
chat-completions with response_format json_object; degraded-marker
code preserved. 12 new tests, HTTP fully mocked.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:37:06 -04:00
John SmithandClaude 9367081b37 feat(gateway): honest degradation markers on SSE narrative fallback (iteration 5)
When the LLM upstream fails (no key, non-200, mid-stream error), the
deterministic fallback generator now announces itself: a sentinel from
stream_narrative makes the gateway emit a leading {degraded, reason}
frame and tag every subsequent frame degraded:true. Healthy streams
carry no degraded key at all. Non-streaming generation gains
generate_narrative() returning {narrative, degraded, reason?} with
shared canned prose so both paths stay identical. Clients can finally
tell real LLM narration from canned fallback.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:27:11 -04:00
John SmithandClaude 9b6caf12a3 feat(simulation): NPC disposition scoring engine (iteration 2)
Continuous directed relationship scores per (npc, player) pair per
GOALS.md Pillar 8: trust and fear with independent exponential decay
(fear half-life ~3.5x shorter), alignment-compatibility bias, NPC-level
stress that amplifies fear, event-driven interaction recording with
strict kind validation, stance banding (hostile..allied), and lazy
decay computed at query time so scores are pure functions of recorded
events. Injectable clock keeps it deterministic for replay/testing.
Pydantic v2 record model matches project conventions. 15 new tests.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:20:54 -04:00
John SmithandClaude fdc9f4a6d4 feat(playtest): measurable AFPR + auditor recall probes
AFPR's rejection branch was mathematically unreachable (run_turn_cycle
only returns COMMITTED / FALLBACK_COMMITTED), so false positives were
tautologically 0%. Now:
- grounded proposals that get rejected ARE counted as auditor false
  positives (a grounded draft describes the genuine engine outcome);
- ~10% of audited turns inject a recall probe — a draft narrating the
  death of a target live state says is alive — which the auditor MUST
  catch; new auditor_recall_pct metric (target >= 95%) reported by the
  benchmark suite;
- entity counts fed to the audit come from the live session snapshot.

Live run: MCR 100%, HCI 1.0, AFPR 0.0%, recall 16/16 = 100%.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:08:47 -04:00
John SmithandClaude 96ced54af3 feat(auditor): ground truth from live engine, audit-before-yield streaming, narrative lore triples
- Grounding: NarrativeGenerateRequest gains engine_session_id /
  target_entity_id. When a session is named, _resolve_audit_inputs fetches
  the live snapshot and the auditor reconciles against IT: lethality fields
  come from the live entity (a payload claiming death while the engine says
  25 HP can no longer smuggle a kill past Vector 3), entity counts come
  from the live snapshot, named-but-missing targets are rejected as ghost
  entities, and an unreachable engine fails closed with
  WORLD_INSPECTOR_UNAVAILABLE instead of auditing stale client claims.
- Streaming: tokens are now HELD until the sentence they belong to has
  passed the auditor; violating sentences and everything after them are
  never emitted — only the corrective SYSTEM frame and the cut.
- Lore continuity: Vector 4 was dead code (nothing ever populated its
  lore_* payload keys). The auditor now derives (subject, predicate,
  object) triples from canon entity names and predicate verbs appearing in
  the draft itself, including reflexive assertions like "Oakhaven Keep
  still stands". Payload triples still take precedence.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:08:47 -04:00
John SmithandClaude a7dc5868b5 fix(homebrew): fail-loud parsing, never fabricate statblocks
Unparseable input used to silently yield a combat-ready fake: AC 14,
HP 30, invented "Claw Strike" action with +5 / 2d6+3. Core fields now
stay absent when not parsed, every missing field is reported in
warnings, and responses carry parse_ok. Strict mode (new flag on
HomebrewParseRequest) rejects with 422 instead of returning a
half-invented statblock. Placeholder lists are copied per call to stop
cross-parse state leaks.

Co-Authored-By: Claude <[email protected]>
2026-08-24 01:08:27 -04:00
John SmithandClaude a0d724e8dc fix(playtest): honest benchmark reporting — repair quoting, respawn sparring target
The step-4 report block had escaped quotes inside f-strings passed through
python -c, a guaranteed SyntaxError that suppressed live MCR/HCI/AFPR output
entirely. Replaced with a quoted-delimiter heredoc so the Python reaches the
interpreter byte-for-byte, and pinned AUTH_SECRET so the fail-closed engine
and the playtest signer agree.

First honest run then exposed a harness bug: the runner kept attacking its
training dummy after killing it (~100 turns of TARGET_ALREADY_DEAD rejections
dragging MCR to 76%). The runner now replaces the dummy when it drops, so
MCR measures action acceptance against living targets. Result: MCR 100%,
HCI 1.0, AFPR 0.0%, trust boundary 8/8.

Co-Authored-By: Claude <[email protected]>
2026-08-24 00:43:06 -04:00
John SmithandClaude beef779d51 fix(gateway): strict CORS allowlist, forward caller identity to engine proxies
- CORS: replace wildcard origins + credentials with an env-driven
  allowlist (CORS_ALLOW_ORIGINS), mirroring the engine's strict_cors.
- identity: engine proxies (spawn/cast-spell/move/turn-next/damage/
  reactions-arm, plus attack and death-save) now verify the caller's
  token and mint a forwarded-identity HMAC token carrying the real
  user_id and role, so the engine's RBAC authorizes the actual actor.
  Previously every proxied action spoke as 'orchestrator-service' with
  no role and was rejected ENTITY_NOT_OWNED on player-owned entities —
  deployed characters could not be driven end-to-end. Omitting the
  token keeps legacy service-mediated behavior.
- tests: proxy identity forwarding, 401 on invalid tokens, contract
  route updated to act as GM.

Co-Authored-By: Claude <[email protected]>
2026-08-24 00:42:52 -04:00
John SmithandClaude 9f31b5db26 feat(wave16): lobbies, character persistence, engine proxies, observability, rate limits
Closes the five product gaps surfaced by the campaign-harness exploration:
- Lobby REST API: create/join-by-invite-code/launch with dual-mode storage
  (Postgres lobbies+lobby_members / MemoryStore), host-only launch binding
  an engine session; LobbyView wired to real roster + invite code
- Character persistence: player_characters table + CRUD; deploy endpoint
  materializes a stat block as an RBAC-owned engine entity
  (owner_player_id = deploying user); CharacterBuilder persists on deploy
- Orchestrator proxy routes for spawn/cast-spell/move/turn-next/damage/
  reactions-arm (service-token authenticated, EngineRejectedError mapping)
- Observability: JSONL LLM call logger (tools + stream paths) and FastAPI
  request logging middleware; mitmproxy opt-in documented for MCP capture
- Rate limiting: in-memory sliding window (auth 30/min, agent 60/min,
  default 600/min) returning 429 + Retry-After
- Engine: orchestrator-service principal trusted to bind ownership on
  behalf of authenticated players during mediated spawns

Co-Authored-By: Claude <[email protected]>
2026-08-23 23:16:55 -04:00
John SmithandClaude 18239bf7b5 feat(wave15): zero-trust hardening, real WFC, write-through Postgres, RBAC, Yjs default
Phase 1-5 remediation + four-gap closure:
- Authoritative engine: ids-only combat payloads (deny_unknown_fields),
  server-side stat blocks, ActionBudget consumption, cover->AC, LoS gating,
  difficult-terrain Dijkstra, HMAC auth middleware + strict CORS
- Honest metrics: live-engine synthetic playtest with trust-boundary probes,
  MCR = valid/(valid+rejected), benchmark script boots its own engine
- Mechanics: condition lifecycle timers, spell slot economy w/ damage caps,
  topological ModifierSource graph, reaction stack (Shield/Counterspell/OA),
  X-card full ledger replay restoration
- Persistence: sqlx write-through tailer to event_sourcing_log,
  snapshot persist/hydrate bridge, recursive CTE inventory functions
- vtt-wfc: dead solver activated (reseed-retry contradictions, flood-fill
  single-region guarantee, deterministic dressing); fixed template removed
- RBAC: role claims in gateway tokens, identity propagation, entity
  ownership, spectator/owner enforcement, fog layer ownership at WS
- Client: PixiJS v8 board layer, physics dice with server-predetermined
  faces, Silero VAD, HRTF PannerNode chain, Y.Doc CRDT default transport
  with HMAC-authenticated ysync relay and LWW fallback

Co-Authored-By: Claude <[email protected]>
2026-08-23 22:41:53 -04:00
John SmithandClaude 013677eb4d feat(wave13): UI/UX hardening — code splitting, dice history, a11y & honest ambience
Perf:
- Route-level code splitting (React.lazy + Suspense): entry chunk 1.9 MB → 241 kB,
  30 on-demand view/modal chunks, Vite chunk-size warning gone

Features:
- DiceHistoryPanel: session audit log of every engine-resolved roll with
  crit/fumble highlighting, 50-entry cap, localStorage persistence
- ShortcutsModal + global keys: `?` cheat-sheet, Escape closes top-most modal,
  typing guard; "Keyboard Shortcuts" entry in Cmd+K palette
- Real WebAudio ambience engine (startAmbience/stopAmbience/setAmbienceVolume):
  jukebox presets synthesize per-track noise+drone loops; play state is now
  honest (opens paused) and master slider is live

Bug fixes:
- pyproject: declare reportlab (6 test modules failed collection on fresh envs)
- Navbar clipped X-CARD at 1366×768: responsive collapse, 0 overflow 900→1920px
- Canvas tool rail clipped under character dock: flex-wrap + max-width
- Macro quickbar occluded ~80% of chat console: collapsed by default
- Alarm-fatigue sounds removed from pagination/mic-toggle/seat-pick

A11y:
- Global :focus-visible rings, prefers-reduced-motion, .selectable-text
- aria-labels on icon-only controls; all modal close buttons labeled + autoFocus
  so focus enters dialogs on open; unlabeled form fields fixed (0 remaining)

SEO: meta description, <main> landmark, robots.txt, spec-compliant llms.txt

Verified: cargo ✅, pytest 57/1 ✅, MCR 100% / HCI 1.0 ✅, build ~1s ✅,
Lighthouse 100/100/100, Chrome DevTools sweeps 900→1920px with 0 visible
overflows. Details in docs/improvement-log.md.

Co-Authored-By: Claude <[email protected]>
2026-08-23 15:16:15 -04:00
John SmithandClaude 6aef9e73b8 feat(wave12): Postgres persistence & campaign save/load
- storage.py: dual-mode store — PostgresStore (asyncpg pool) when
  DATABASE_URL is reachable, MemoryStore fallback otherwise; idempotent
  _ensure_tables() DDL at startup; PBKDF2 credential helpers centralized
- 03_campaign_persistence.sql: narrative_state.users, campaigns (anchors
  the previously free-floating campaign_id), campaign_saves (JSONB
  snapshots) + owner/updated index — also auto-run by compose init
- auth routes now persist users across restarts (verified live: seeded GM
  account survives process restarts via Postgres)
- new token-guarded API: POST /api/v1/campaign/save (upsert by owner+name),
  GET /campaign/saves, GET /campaign/save/{id}, DELETE /campaign/save/{id}
- client: CampaignSaveModal (save/list/load/delete with round + timestamp),
  Campaign Saves entry in Table Tools dropdown, snapshot build/apply in App
- fix compose postgres crash-loop on SELinux systems (:ro,z relabel flag)
- tests: memory-mode API CRUD always runs; PG end-to-end incl. cross-owner
  denial and durability-across-reinit skips without DATABASE_URL
  (58 pytest pass with Postgres up, cargo clean, MCR 100%)

Co-Authored-By: Claude <[email protected]>
2026-08-23 10:53:40 -04:00
John SmithandClaude 3920b10e59 feat(wave11): SRD-grounded narration, end-to-end X-card rewind & honest voice activity
- narrative stream: extract_srd_context() scans player intents against the
  SRD 5.2 bestiary/grimoire and injects stat-block facts (AC/HP/actions,
  spell level/school/description) into both live LLM prompts and the
  deterministic mock generator — the DM can no longer contradict the rules
- new GET /api/v1/compendium/lore-lookup for client-side fact resolution
- safety: X-card now applies a real ledger rewind on the engine session
  (POST /sessions/{id}/safety/x-card) via the orchestrator, recording
  SAFETY_REWIND_APPLIED audit events; client wires the SafetyModal through
- voice: removed the random speaking simulation from WebRTCMeshManager;
  local mic RMS (getUserMedia + AnalyserNode) drives the real speaking
  ring; remote peers stay silent until genuine P2P audio lands
- tests: +7 grounding/x-card suite (53 pytest total pass), cargo clean,
  client ~1s build, MCR 100% / HCI 1.0 / AFPR 0%

Co-Authored-By: Claude <[email protected]>
2026-08-23 01:35:50 -04:00
John SmithandClaude ce03c0fae0 feat(wave10): real multiplayer backbone — WebSocket CRDT relay, live presence & server-backed auth
- vtt-server: activate the dormant CrdtRelayHub with a live PeerRegistry and
  actix-ws endpoint /ws/sessions/{id}/sync (root + /api/v1 alias). Token moves
  pass LWW vector-clock arbitration before fan-out; stale transforms are
  suppressed. GET /rooms/{id}/presence reports connected peers.
- client: VttCrdtSyncClient gains remote-update listeners, exponential
  backoff reconnect, solo-safe no-ops; App.tsx instantiates it, broadcasts
  token drags, applies peer updates; LobbyView polls live relay presence
- orchestrator: /api/v1/auth/{signup,login,session} — PBKDF2-salted password
  hashes, HMAC-signed 12h tokens (AUTH_SECRET env), default GM seed account;
  AuthModal uses the server with demo-account fallback offline
- tests: +5 auth suite; all suites green (46 pytest engine-up, cargo clean,
  client ~1s build, MCR 100% / HCI 1.0)

Co-Authored-By: Claude <[email protected]>
2026-08-23 01:23:15 -04:00
John SmithandClaude d2af1487bd feat(wave9): resolve all dice through the authoritative Rust rules engine
- vtt-server: new POST /api/v1/actions/{save,concentration,death-save}
  handlers backed by vtt-core RulesEvaluator; /actions/check now honors
  advantage/disadvantage
- orchestrator: routing/engine_client.py + /api/v1/engine/* proxy routes
  (session, attack, check, save, concentration, death-save, map/generate)
  with 502 on engine-down; ENGINE_API_URL env-configured
- client: App.tsx attack/spell/check/macro rolls now resolve server-side
  via api/rules_engine.ts (local dice fallback offline); WfcStudioView
  hits the real engine WFC route and maps its Vec<Vec<u8>> tile grid
- tests: 8 proxy tests (live-engine integration + 502 contract), all
  suites green: 41 pytest pass engine-up / 34+7 skip engine-down,
  cargo workspace clean, client builds in ~1s, MCR 100% / HCI 1.0
- fix misleading "ALL PHASES 1-7" benchmark banner

Co-Authored-By: Claude <[email protected]>
2026-08-23 01:00:30 -04:00
John SmithandClaude Opus 5 07ac5ce9ca feat(wave8): expand compendium with SRD 5.2 magic items, feats, origins, animals & full stat blocks
- New srd52_importer.py parses the flat-file SRD 5.2.1 markdown layout
  (downfallx/dnd-5e-srd-markdown, CC BY 4.0) into 7 fixture domains:
  235 monsters with complete trait/action/legendary sections (was 28 of
  318 shells), 352 spells with untruncated descriptions + upcast text,
  260 magic items with rarity/attunement, 95 animal stat blocks,
  17 feats, 8 backgrounds + 9 species origins, 154 glossary terms
- Server: prefer srd_5_2 fixtures (5.1 fallback); add /api/v1/compendium
  routes for magic-items, feats, origins, animals, glossary
- Client Codex: new Magic Items / Feats / Animals / Glossary tabs reusing
  pagination engine; rarity chips; animal spawn-to-canvas; attribution footer
- CommandPalette: live-indexes spells/items/feats/glossary over Cmd+K search
- Fix mismatched API paths in CharacterBuilderView and BundleManagerView
- Add ATTRIBUTION.md (Wizards of the Coast SRD, CC BY 4.0)
- Tests: +12 importer tests against checked-in sample markdown (33 total pass)

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-23 00:27:49 -04:00
John Smith 3ed3815820 feat: complete Horizons 1, 2, and 3 (Docker Compose, Procedural Quest Engine & Concordia Pacts, and 3D Elevation) 2026-08-22 19:49:55 -04:00
John Smith 82125ed1a6 feat(srd): parse D&D 5e SRD markdown into hardcoded VTT core logic, schemas, and relational tables 2026-08-22 19:40:57 -04:00
John Smith 774c68410a feat(campaign-bundles-homebrew): implement COMPASS .vttbundle packaging, Homebrewery markdown parser, and bundle manager studio 2026-08-22 19:31:12 -04:00
John Smith 8ef12b5e4d feat(dynasty-empirical-playtest): implement opendnd dynasty lineages, feud matrix, and dnddata empirical party benchmarks 2026-08-22 19:27:54 -04:00
John Smith 04400c31a6 feat(character-builder-pdf): implement 5e character studio, strict modifier graph, and vector PDF exporter 2026-08-22 19:12:08 -04:00