Compare commits
1 Commits
mimo/creat
...
mimo/code/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b2ac5cd1f |
@@ -1,169 +1,132 @@
|
||||
# Legacy Matrix Audit — Migration Table
|
||||
# Legacy Matrix Audit
|
||||
|
||||
Purpose:
|
||||
Preserve quality work from `/Users/apayne/the-matrix` before the Nexus browser shell is rebuilt.
|
||||
Preserve useful work from `/Users/apayne/the-matrix` before the Nexus browser shell is rebuilt.
|
||||
|
||||
Canonical rule:
|
||||
- `Timmy_Foundation/the-nexus` is the only canonical 3D repo.
|
||||
- `/Users/apayne/the-matrix` is legacy source material, not a parallel product.
|
||||
- This document is the authoritative migration table for issue #685.
|
||||
|
||||
## Verified Legacy State
|
||||
## Verified Legacy Matrix State
|
||||
|
||||
Local legacy repo: `/Users/apayne/the-matrix`
|
||||
Local legacy repo:
|
||||
- `/Users/apayne/the-matrix`
|
||||
|
||||
Observed facts:
|
||||
- Vite browser app, vanilla JS + Three.js 0.171.0
|
||||
- 24 JS modules under `js/`
|
||||
- Smoke suite: 87 passed, 0 failed
|
||||
- Package scripts: dev, build, preview, test
|
||||
- PWA manifest + service worker
|
||||
- Vite config with code-splitting (Three.js in separate chunk)
|
||||
- Quality-tier system for hardware detection
|
||||
- WebSocket client with reconnection, heartbeat, mock mode
|
||||
- Full avatar FPS movement + PiP camera
|
||||
- Sub-world portal system with zone triggers
|
||||
- Vite browser app exists
|
||||
- `npm test` passes with `87 passed, 0 failed`
|
||||
- 23 JS modules under `js/`
|
||||
- package scripts include `dev`, `build`, `preview`, and `test`
|
||||
|
||||
## Migration Table
|
||||
## Known historical Nexus snapshot
|
||||
|
||||
Decision key:
|
||||
- **CARRY** = transplant concepts and patterns into Nexus vNext
|
||||
- **ARCHIVE** = keep as reference, do not directly transplant
|
||||
- **DROP** = do not preserve unless re-justified
|
||||
Useful in-repo reference point:
|
||||
- `0518a1c3ae3c1d0afeb24dea9772102f5a3d9a66`
|
||||
|
||||
### Core Modules
|
||||
That snapshot still contains browser-world root files such as:
|
||||
- `index.html`
|
||||
- `app.js`
|
||||
- `style.css`
|
||||
- `package.json`
|
||||
- `tests/`
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/main.js` | 180 | App bootstrap, render loop, WebGL context recovery | **CARRY** | Architectural pattern. Shows clean init/teardown lifecycle, context-loss recovery, visibility pause. Nexus needs this loop but should not copy the monolithic wiring. |
|
||||
| `js/world.js` | 95 | Scene, camera, renderer, grid, lights | **CARRY** | Foundational. Quality-tier-aware renderer setup, grid floor, lighting. Nexus already has a world but should adopt the tier-aware antialiasing and pixel-ratio capping. |
|
||||
| `js/config.js` | 68 | Connection config via URL params + env vars | **ARCHIVE** | Pattern reference only. Nexus config should route through Hermes harness, not Vite env vars. The URL-override pattern (ws, token, mock) is worth remembering. |
|
||||
| `js/quality.js` | 90 | Hardware detection, quality tier (low/medium/high) | **CARRY** | Directly useful. DPR capping, core/memory/screen heuristics, WebGL renderer sniffing. Nexus needs this for graceful degradation on Mac/iPad. |
|
||||
| `js/storage.js` | 39 | Safe localStorage with in-memory fallback | **CARRY** | Small, robust, sandbox-proof. Nexus should use this or equivalent. Prevents crashes in sandboxed iframes. |
|
||||
## Rescue Candidates
|
||||
|
||||
### Agent System
|
||||
### Carry forward into Nexus vNext
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/agent-defs.js` | 30 | Agent identity data (id, label, color, role, position) | **CARRY** | Seed data model. Nexus agents should be defined similarly — data-driven, not hardcoded in render logic. Color hex helper is trivial but useful. |
|
||||
| `js/agents.js` | 523 | Agent 3D objects, movement, state, connection lines, hot-add/remove | **CARRY** | Core visual system. Shared geometries (perf), movement interpolation, wallet-health stress glow, auto-placement algorithm, connection-line pulse. All valuable. Needs integration with real agent state from Hermes. |
|
||||
| `js/behaviors.js` | 413 | Autonomous agent behavior state machine | **ARCHIVE** | Pattern reference. The personality-weighted behavior selection, conversation pairing, and artifact-placement system are well-designed. But Nexus behaviors should be driven by Hermes, not a client-side simulation. Keep the architecture, drop the fake-autonomy. |
|
||||
| `js/presence.js` | 139 | Agent presence HUD (online/offline, uptime, state) | **CARRY** | Valuable UX. Live "who's here" panel with uptime tickers and state indicators. Needs real backend state, not mock assumptions. |
|
||||
1. `agent-defs.js`
|
||||
- agent identity definitions
|
||||
- useful as seed data/model for visible entities in the world
|
||||
|
||||
### Visitor & Interaction
|
||||
2. `agents.js`
|
||||
- agent objects, state machine, connection lines
|
||||
- useful for visualizing Timmy / subagents / system processes in a world-native way
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/visitor.js` | 141 | Visitor enter/leave protocol, chat input | **CARRY** | Session lifecycle. Device detection, visibility-based leave/return, chat input wiring. Directly applicable to Nexus visitor tracking. |
|
||||
| `js/avatar.js` | 360 | FPS movement, PiP dual-camera, touch input | **CARRY** | Visitor embodiment. WASD + arrow movement, first/third person swap, PiP canvas, touch joystick, right-click mouse-look. Strong work. Needs tuning for Nexus world bounds. |
|
||||
| `js/interaction.js` | 296 | Raycasting, click-to-select agents, info popup | **CARRY** | Essential for any browser world. OrbitControls, pointer/tap detection, agent popup with state/role, TALK button. The popup-anchoring-to-3D-position logic is particularly well done. |
|
||||
| `js/zones.js` | 161 | Proximity trigger zones (portal enter/exit, events) | **CARRY** | Spatial event system. Portal traversal, event triggers, once-only zones. Nexus portals (#672) need this exact pattern. |
|
||||
3. `avatar.js`
|
||||
- visitor embodiment, movement, camera handling
|
||||
- strongly aligned with "training ground" and "walk the world" goals
|
||||
|
||||
### Chat & Communication
|
||||
4. `ui.js`
|
||||
- HUD, chat surfaces, overlays
|
||||
- useful if rebuilt against real harness data instead of stale fake state
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/bark.js` | 141 | Speech bubble system with typing animation | **CARRY** | Timmy's voice in-world. Typing animation, queue, auto-dismiss, emotion tags, demo bark lines. Strong expressive presence. The demo lines ("The Tower watches. The Tower remembers.") are good seed content. |
|
||||
| `js/ui.js` | 285 | Chat panel, agent list, HUD, streaming tokens | **CARRY** | Chat infrastructure. Rolling chat buffer, per-agent localStorage history, streaming token display with cursor animation, HTML escaping. Needs reconnection to Hermes chat instead of WS mock. |
|
||||
| `js/transcript.js` | 183 | Conversation transcript logger, export | **ARCHIVE** | Pattern reference. The rolling buffer, structured JSON entries, TXT/JSON download, HUD badge are all solid. But transcript authority should live in Hermes, not browser localStorage. Keep the UX pattern, rebuild storage layer. |
|
||||
5. `websocket.js`
|
||||
- browser-side live bridge patterns
|
||||
- useful if retethered to Hermes-facing transport
|
||||
|
||||
### Visual Effects
|
||||
6. `transcript.js`
|
||||
- local transcript capture pattern
|
||||
- useful if durable truth still routes through Hermes and browser cache remains secondary
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/effects.js` | 195 | Matrix rain particles + starfield | **CARRY** | Atmospheric foundation. Quality-tier particle counts, frame-skip optimization, adaptive draw-range (FPS-budget recovery), bounding-sphere pre-compute. This is production-grade particle work. |
|
||||
| `js/ambient.js` | 212 | Mood-driven atmosphere (lighting, fog, rain, stars) | **CARRY** | Scene mood engine. Smooth eased transitions between mood states (calm, focused, excited, contemplative, stressed), per-mood lighting/fog/rain/star parameters. Directly supports Nexus atmosphere. |
|
||||
| `js/satflow.js` | 261 | Lightning payment particle flow | **CARRY** | Economy visualization. Bezier-arc particles, staggered travel, burst-on-arrival, pooling. If Nexus shows any payment/economy flow, this is the pattern. |
|
||||
7. `ambient.js`
|
||||
- mood / atmosphere system
|
||||
- directly supports wizardly presentation without changing system authority
|
||||
|
||||
### Economy & Scene
|
||||
8. `satflow.js`
|
||||
- visual economy / payment flow motifs
|
||||
- useful if Timmy's economy/agent interactions become a real visible layer
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/economy.js` | 100 | Wallet/treasury HUD panel | **ARCHIVE** | UI pattern reference. Clean sats formatting, per-agent balance rows, health-colored dots, recent transactions. Worth rebuilding when backed by real sovereign metrics. |
|
||||
| `js/scene-objects.js` | 718 | Dynamic 3D object registry, portals, sub-worlds | **CARRY** | Critical. Geometry/material factories, animation system (rotate/bob/pulse/orbit), portal visual (torus ring + glow disc + zone), sub-world load/unload, text sprites, compound groups. This is the most complex and valuable module. Nexus portals (#672) should build on this. |
|
||||
9. `economy.js`
|
||||
- treasury / wallet panel ideas
|
||||
- useful if later backed by real sovereign metrics
|
||||
|
||||
### Backend Bridge
|
||||
10. `presence.js`
|
||||
- who-is-here / online-state UI
|
||||
- useful for showing human + agent + process presence in the world
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `js/websocket.js` | 598 | WebSocket client, message dispatcher, mock mode | **ARCHIVE** | Pattern reference only. Reconnection with exponential backoff, heartbeat/zombie detection, rich message dispatch (40+ message types), streaming chat support. The architecture is sound but must be reconnected to Hermes transport, not copied wholesale. The message-type catalog is the most valuable reference artifact. |
|
||||
| `js/demo.js` | ~300 | Demo autopilot (mock mode simulation) | **DROP** | Fake activity simulation. Deliberately creates the illusion of live data. Do not preserve. If Nexus needs a demo mode, build a clearly-labeled one that doesn't pretend to be real. |
|
||||
11. `interaction.js`
|
||||
- clicking, inspecting, selecting world entities
|
||||
- likely needed in any real browser-facing Nexus shell
|
||||
|
||||
### Testing & Build
|
||||
12. `quality.js`
|
||||
- hardware-aware quality tiering
|
||||
- useful for local-first graceful degradation on Mac hardware
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `test/smoke.mjs` | 235 | Automated browser smoke test suite | **CARRY** | Testing discipline. Module inventory check, export verification, HTML structure validation, Vite build test, bundle-size budget, PWA manifest check. Nexus should adopt this pattern (adapted for its own module structure). |
|
||||
| `vite.config.js` | 53 | Build config with code splitting, SW generation | **ARCHIVE** | Build tooling reference. manualChunks for Three.js, SW precache generation plugin. Relevant if Nexus re-commits to Vite. |
|
||||
| `sw.js` | ~40 | Service worker with precache | **ARCHIVE** | PWA reference. Relevant only if Nexus pursues offline-first PWA. |
|
||||
| `manifest.json` | ~20 | PWA manifest | **ARCHIVE** | PWA reference. |
|
||||
13. `bark.js`
|
||||
- prominent speech / bark system
|
||||
- strong fit for Timmy's expressive presence in-world
|
||||
|
||||
### Server-Side (Python)
|
||||
14. `world.js`, `effects.js`, `scene-objects.js`, `zones.js`
|
||||
- broad visual foundation work
|
||||
- should be mined for patterns, not blindly transplanted
|
||||
|
||||
| File | Lines | Capability | Decision | Why for Nexus |
|
||||
|------|-------|------------|----------|---------------|
|
||||
| `server/bridge.py` | ~900 | WebSocket bridge server | **ARCHIVE** | Reference. Hermes replaces this role. Keep for protocol schema reference. |
|
||||
| `server/gateway.py` | ~400 | HTTP gateway | **ARCHIVE** | Reference. |
|
||||
| `server/ollama_client.py` | ~280 | Ollama integration | **ARCHIVE** | Reference. Relevant if Nexus needs local model calls. |
|
||||
| `server/research.py` | ~450 | Research pipeline | **ARCHIVE** | Reference. |
|
||||
| `server/webhooks.py` | ~350 | Webhook handler | **ARCHIVE** | Reference. |
|
||||
| `server/test_*.py` | ~5 files | Server test suites | **ARCHIVE** | Testing patterns worth studying. |
|
||||
15. `test/smoke.mjs`
|
||||
- browser smoke discipline
|
||||
- should inform rebuilt validation in canonical Nexus repo
|
||||
|
||||
## Summary by Decision
|
||||
### Archive as reference, not direct carry-forward
|
||||
|
||||
### CARRY FORWARD (17 modules)
|
||||
These modules contain patterns, algorithms, or entire implementations that should move into the Nexus browser shell:
|
||||
- demo/autopilot assumptions that pretend fake backend activity is real
|
||||
- any websocket schema that no longer matches Hermes truth
|
||||
- Vite-specific plumbing that is only useful if we consciously recommit to Vite
|
||||
|
||||
- `quality.js` — hardware detection
|
||||
- `storage.js` — safe persistence
|
||||
- `world.js` — scene foundation
|
||||
- `agent-defs.js` — agent data model
|
||||
- `agents.js` — agent visualization + movement
|
||||
- `presence.js` — online presence HUD
|
||||
- `visitor.js` — session lifecycle
|
||||
- `avatar.js` — FPS embodiment
|
||||
- `interaction.js` — click/select/raycast
|
||||
- `zones.js` — spatial triggers
|
||||
- `bark.js` — speech bubbles
|
||||
- `ui.js` — chat/HUD
|
||||
- `effects.js` — particle effects
|
||||
- `ambient.js` — mood atmosphere
|
||||
- `satflow.js` — payment flow particles
|
||||
- `scene-objects.js` — dynamic objects + portals
|
||||
- `test/smoke.mjs` — smoke test discipline
|
||||
### Deliberately drop unless re-justified
|
||||
|
||||
### ARCHIVE AS REFERENCE (9 modules/files)
|
||||
Keep for patterns, protocol schemas, and architectural reference. Do not directly transplant:
|
||||
|
||||
- `config.js` — config pattern (use Hermes instead)
|
||||
- `behaviors.js` — behavior architecture (use Hermes-driven state)
|
||||
- `transcript.js` — transcript UX (use Hermes storage)
|
||||
- `economy.js` — economy UI pattern (use real metrics)
|
||||
- `websocket.js` — message protocol catalog + reconnection patterns
|
||||
- `vite.config.js` — build tooling
|
||||
- `sw.js`, `manifest.json` — PWA reference
|
||||
- `server/*.py` — server protocol schemas
|
||||
|
||||
### DELIBERATELY DROP (2)
|
||||
Do not preserve unless re-justified:
|
||||
|
||||
- `demo.js` — fake activity simulation; creates false impression of live system
|
||||
- `main.js` monolithic wiring — the init pattern carries, the specific module wiring does not
|
||||
- anything that presents mock data as if it were live
|
||||
- anything that duplicates a better Hermes-native telemetry path
|
||||
- anything that turns the browser into the system of record
|
||||
|
||||
## Concern Separation for Nexus vNext
|
||||
|
||||
When rebuilding inside `the-nexus`, keep these concerns in separate modules:
|
||||
When rebuilding inside `the-nexus`, keep concerns separated:
|
||||
|
||||
1. **World shell** — scene, camera, renderer, grid, lights, fog
|
||||
2. **Effects layer** — rain, stars, ambient mood transitions
|
||||
3. **Agent visualization** — 3D objects, labels, connection lines, movement
|
||||
4. **Visitor embodiment** — avatar, FPS controls, PiP camera
|
||||
5. **Interaction layer** — raycasting, selection, zones, portal traversal
|
||||
6. **Communication surface** — bark, chat panel, streaming tokens
|
||||
7. **Presence & HUD** — who's-online, economy panel, transcript controls
|
||||
8. **Harness bridge** — WebSocket/API transport to Hermes (NOT a copy of websocket.js)
|
||||
9. **Quality & config** — hardware detection, runtime configuration
|
||||
10. **Smoke tests** — automated validation
|
||||
1. World shell / rendering
|
||||
- scene, camera, movement, atmosphere
|
||||
|
||||
2. Presence and embodiment
|
||||
- avatar, agent placement, selection, bark/chat surfaces
|
||||
|
||||
3. Harness bridge
|
||||
- websocket / API bridge from Hermes truth into browser state
|
||||
|
||||
4. Visualization panels
|
||||
- metrics, presence, economy, portal states, transcripts
|
||||
|
||||
5. Validation
|
||||
- smoke tests, screenshot proof, provenance checks
|
||||
|
||||
6. Game portal layer
|
||||
- Morrowind / portal-specific interaction surfaces
|
||||
|
||||
Do not collapse all of this into one giant app file again.
|
||||
Do not let visual shell code become telemetry authority.
|
||||
|
||||
@@ -29,6 +29,8 @@ from typing import Any, Callable, Optional
|
||||
|
||||
import websockets
|
||||
|
||||
from bannerlord_trace import BannerlordTraceLogger
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# CONFIGURATION
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -265,11 +267,13 @@ class BannerlordHarness:
|
||||
desktop_command: Optional[list[str]] = None,
|
||||
steam_command: Optional[list[str]] = None,
|
||||
enable_mock: bool = False,
|
||||
enable_trace: bool = False,
|
||||
):
|
||||
self.hermes_ws_url = hermes_ws_url
|
||||
self.desktop_command = desktop_command or DEFAULT_MCP_DESKTOP_COMMAND
|
||||
self.steam_command = steam_command or DEFAULT_MCP_STEAM_COMMAND
|
||||
self.enable_mock = enable_mock
|
||||
self.enable_trace = enable_trace
|
||||
|
||||
# MCP clients
|
||||
self.desktop_mcp: Optional[MCPClient] = None
|
||||
@@ -284,6 +288,9 @@ class BannerlordHarness:
|
||||
self.cycle_count = 0
|
||||
self.running = False
|
||||
|
||||
# Session trace logger
|
||||
self.trace_logger: Optional[BannerlordTraceLogger] = None
|
||||
|
||||
# ═══ LIFECYCLE ═══
|
||||
|
||||
async def start(self) -> bool:
|
||||
@@ -314,6 +321,15 @@ class BannerlordHarness:
|
||||
# Connect to Hermes WebSocket
|
||||
await self._connect_hermes()
|
||||
|
||||
# Initialize trace logger if enabled
|
||||
if self.enable_trace:
|
||||
self.trace_logger = BannerlordTraceLogger(
|
||||
harness_session_id=self.session_id,
|
||||
hermes_session_id=self.session_id,
|
||||
)
|
||||
self.trace_logger.start_session()
|
||||
log.info(f"Trace logger started: {self.trace_logger.trace_id}")
|
||||
|
||||
log.info("Harness initialized successfully")
|
||||
return True
|
||||
|
||||
@@ -322,6 +338,12 @@ class BannerlordHarness:
|
||||
self.running = False
|
||||
log.info("Shutting down harness...")
|
||||
|
||||
# Finalize trace logger
|
||||
if self.trace_logger:
|
||||
manifest = self.trace_logger.finish_session()
|
||||
log.info(f"Trace saved: {manifest.trace_file}")
|
||||
log.info(f"Manifest: {self.trace_logger.manifest_file}")
|
||||
|
||||
if self.desktop_mcp:
|
||||
self.desktop_mcp.stop()
|
||||
if self.steam_mcp:
|
||||
@@ -707,6 +729,11 @@ class BannerlordHarness:
|
||||
self.cycle_count = iteration
|
||||
log.info(f"\n--- ODA Cycle {iteration + 1}/{max_iterations} ---")
|
||||
|
||||
# Start trace cycle
|
||||
trace_cycle = None
|
||||
if self.trace_logger:
|
||||
trace_cycle = self.trace_logger.begin_cycle(iteration)
|
||||
|
||||
# 1. OBSERVE: Capture state
|
||||
log.info("[OBSERVE] Capturing game state...")
|
||||
state = await self.capture_state()
|
||||
@@ -715,11 +742,24 @@ class BannerlordHarness:
|
||||
log.info(f" Screen: {state.visual.screen_size}")
|
||||
log.info(f" Players online: {state.game_context.current_players_online}")
|
||||
|
||||
# Populate trace with observation data
|
||||
if trace_cycle:
|
||||
trace_cycle.screenshot_path = state.visual.screenshot_path or ""
|
||||
trace_cycle.window_found = state.visual.window_found
|
||||
trace_cycle.screen_size = list(state.visual.screen_size)
|
||||
trace_cycle.mouse_position = list(state.visual.mouse_position)
|
||||
trace_cycle.playtime_hours = state.game_context.playtime_hours
|
||||
trace_cycle.players_online = state.game_context.current_players_online
|
||||
trace_cycle.is_running = state.game_context.is_running
|
||||
|
||||
# 2. DECIDE: Get actions from decision function
|
||||
log.info("[DECIDE] Getting actions...")
|
||||
actions = decision_fn(state)
|
||||
log.info(f" Decision returned {len(actions)} actions")
|
||||
|
||||
if trace_cycle:
|
||||
trace_cycle.actions_planned = actions
|
||||
|
||||
# 3. ACT: Execute actions
|
||||
log.info("[ACT] Executing actions...")
|
||||
results = []
|
||||
@@ -731,6 +771,13 @@ class BannerlordHarness:
|
||||
if result.error:
|
||||
log.info(f" Error: {result.error}")
|
||||
|
||||
if trace_cycle:
|
||||
trace_cycle.actions_executed.append(result.to_dict())
|
||||
|
||||
# Finalize trace cycle
|
||||
if trace_cycle:
|
||||
self.trace_logger.finish_cycle(trace_cycle)
|
||||
|
||||
# Send cycle summary telemetry
|
||||
await self._send_telemetry({
|
||||
"type": "oda_cycle_complete",
|
||||
@@ -836,12 +883,18 @@ async def main():
|
||||
default=1.0,
|
||||
help="Delay between iterations in seconds (default: 1.0)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--trace",
|
||||
action="store_true",
|
||||
help="Enable session trace logging to ~/.timmy/traces/bannerlord/",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Create harness
|
||||
harness = BannerlordHarness(
|
||||
hermes_ws_url=args.hermes_ws,
|
||||
enable_mock=args.mock,
|
||||
enable_trace=args.trace,
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
234
nexus/bannerlord_trace.py
Normal file
234
nexus/bannerlord_trace.py
Normal file
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Bannerlord Session Trace Logger — First-Replayable Training Material
|
||||
|
||||
Captures one Bannerlord session as a replayable trace:
|
||||
- Timestamps on every cycle
|
||||
- Actions executed with success/failure
|
||||
- World-state evidence (screenshots, Steam stats)
|
||||
- Hermes session/log ID mapping
|
||||
|
||||
Storage: ~/.timmy/traces/bannerlord/trace_<session_id>.jsonl
|
||||
Manifest: ~/.timmy/traces/bannerlord/manifest_<session_id>.json
|
||||
|
||||
Each JSONL line is one ODA cycle with full context.
|
||||
The manifest bundles metadata for replay/eval.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Storage root — local-first under ~/.timmy/
|
||||
DEFAULT_TRACE_DIR = Path.home() / ".timmy" / "traces" / "bannerlord"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CycleTrace:
|
||||
"""One ODA cycle captured in full."""
|
||||
cycle_index: int
|
||||
timestamp_start: str
|
||||
timestamp_end: str = ""
|
||||
duration_ms: int = 0
|
||||
|
||||
# Observe
|
||||
screenshot_path: str = ""
|
||||
window_found: bool = False
|
||||
screen_size: list[int] = field(default_factory=lambda: [1920, 1080])
|
||||
mouse_position: list[int] = field(default_factory=lambda: [0, 0])
|
||||
playtime_hours: float = 0.0
|
||||
players_online: int = 0
|
||||
is_running: bool = False
|
||||
|
||||
# Decide
|
||||
actions_planned: list[dict] = field(default_factory=list)
|
||||
decision_note: str = ""
|
||||
|
||||
# Act
|
||||
actions_executed: list[dict] = field(default_factory=list)
|
||||
actions_succeeded: int = 0
|
||||
actions_failed: int = 0
|
||||
|
||||
# Metadata
|
||||
hermes_session_id: str = ""
|
||||
hermes_log_id: str = ""
|
||||
harness_session_id: str = ""
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionManifest:
|
||||
"""Top-level metadata for a captured session trace."""
|
||||
trace_id: str
|
||||
harness_session_id: str
|
||||
hermes_session_id: str
|
||||
hermes_log_id: str
|
||||
game: str = "Mount & Blade II: Bannerlord"
|
||||
app_id: int = 261550
|
||||
started_at: str = ""
|
||||
finished_at: str = ""
|
||||
total_cycles: int = 0
|
||||
total_actions: int = 0
|
||||
total_succeeded: int = 0
|
||||
total_failed: int = 0
|
||||
trace_file: str = ""
|
||||
trace_dir: str = ""
|
||||
replay_command: str = ""
|
||||
eval_note: str = ""
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class BannerlordTraceLogger:
|
||||
"""
|
||||
Captures a single Bannerlord session as a replayable trace.
|
||||
|
||||
Usage:
|
||||
logger = BannerlordTraceLogger(hermes_session_id="abc123")
|
||||
logger.start_session()
|
||||
cycle = logger.begin_cycle(0)
|
||||
# ... populate cycle fields ...
|
||||
logger.finish_cycle(cycle)
|
||||
manifest = logger.finish_session()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
trace_dir: Optional[Path] = None,
|
||||
harness_session_id: str = "",
|
||||
hermes_session_id: str = "",
|
||||
hermes_log_id: str = "",
|
||||
):
|
||||
self.trace_dir = trace_dir or DEFAULT_TRACE_DIR
|
||||
self.trace_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.trace_id = f"bl_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:6]}"
|
||||
self.harness_session_id = harness_session_id or str(uuid.uuid4())[:8]
|
||||
self.hermes_session_id = hermes_session_id
|
||||
self.hermes_log_id = hermes_log_id
|
||||
|
||||
self.trace_file = self.trace_dir / f"trace_{self.trace_id}.jsonl"
|
||||
self.manifest_file = self.trace_dir / f"manifest_{self.trace_id}.json"
|
||||
|
||||
self.cycles: list[CycleTrace] = []
|
||||
self.started_at: str = ""
|
||||
self.finished_at: str = ""
|
||||
|
||||
def start_session(self) -> str:
|
||||
"""Begin a trace session. Returns trace_id."""
|
||||
self.started_at = datetime.now(timezone.utc).isoformat()
|
||||
return self.trace_id
|
||||
|
||||
def begin_cycle(self, cycle_index: int) -> CycleTrace:
|
||||
"""Start recording one ODA cycle."""
|
||||
cycle = CycleTrace(
|
||||
cycle_index=cycle_index,
|
||||
timestamp_start=datetime.now(timezone.utc).isoformat(),
|
||||
harness_session_id=self.harness_session_id,
|
||||
hermes_session_id=self.hermes_session_id,
|
||||
hermes_log_id=self.hermes_log_id,
|
||||
)
|
||||
return cycle
|
||||
|
||||
def finish_cycle(self, cycle: CycleTrace) -> None:
|
||||
"""Finalize and persist one cycle to the trace file."""
|
||||
cycle.timestamp_end = datetime.now(timezone.utc).isoformat()
|
||||
# Compute duration
|
||||
try:
|
||||
t0 = datetime.fromisoformat(cycle.timestamp_start)
|
||||
t1 = datetime.fromisoformat(cycle.timestamp_end)
|
||||
cycle.duration_ms = int((t1 - t0).total_seconds() * 1000)
|
||||
except (ValueError, TypeError):
|
||||
cycle.duration_ms = 0
|
||||
|
||||
# Count successes/failures
|
||||
cycle.actions_succeeded = sum(
|
||||
1 for a in cycle.actions_executed if a.get("success", False)
|
||||
)
|
||||
cycle.actions_failed = sum(
|
||||
1 for a in cycle.actions_executed if not a.get("success", True)
|
||||
)
|
||||
|
||||
self.cycles.append(cycle)
|
||||
|
||||
# Append to JSONL
|
||||
with open(self.trace_file, "a") as f:
|
||||
f.write(json.dumps(cycle.to_dict()) + "\n")
|
||||
|
||||
def finish_session(self) -> SessionManifest:
|
||||
"""Finalize the session and write the manifest."""
|
||||
self.finished_at = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
total_actions = sum(len(c.actions_executed) for c in self.cycles)
|
||||
total_succeeded = sum(c.actions_succeeded for c in self.cycles)
|
||||
total_failed = sum(c.actions_failed for c in self.cycles)
|
||||
|
||||
manifest = SessionManifest(
|
||||
trace_id=self.trace_id,
|
||||
harness_session_id=self.harness_session_id,
|
||||
hermes_session_id=self.hermes_session_id,
|
||||
hermes_log_id=self.hermes_log_id,
|
||||
started_at=self.started_at,
|
||||
finished_at=self.finished_at,
|
||||
total_cycles=len(self.cycles),
|
||||
total_actions=total_actions,
|
||||
total_succeeded=total_succeeded,
|
||||
total_failed=total_failed,
|
||||
trace_file=str(self.trace_file),
|
||||
trace_dir=str(self.trace_dir),
|
||||
replay_command=(
|
||||
f"python -m nexus.bannerlord_harness --mock --replay {self.trace_file}"
|
||||
),
|
||||
eval_note=(
|
||||
"To replay: load this trace, re-execute each cycle's actions_planned "
|
||||
"against a fresh harness in mock mode, compare actions_executed outcomes. "
|
||||
"Success metric: >=90% action parity between original and replay runs."
|
||||
),
|
||||
)
|
||||
|
||||
with open(self.manifest_file, "w") as f:
|
||||
json.dump(manifest.to_dict(), f, indent=2)
|
||||
|
||||
return manifest
|
||||
|
||||
@classmethod
|
||||
def load_trace(cls, trace_file: Path) -> list[dict]:
|
||||
"""Load a trace JSONL file for replay or analysis."""
|
||||
cycles = []
|
||||
with open(trace_file) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line:
|
||||
cycles.append(json.loads(line))
|
||||
return cycles
|
||||
|
||||
@classmethod
|
||||
def load_manifest(cls, manifest_file: Path) -> dict:
|
||||
"""Load a session manifest."""
|
||||
with open(manifest_file) as f:
|
||||
return json.load(f)
|
||||
|
||||
@classmethod
|
||||
def list_traces(cls, trace_dir: Optional[Path] = None) -> list[dict]:
|
||||
"""List all available trace sessions."""
|
||||
d = trace_dir or DEFAULT_TRACE_DIR
|
||||
if not d.exists():
|
||||
return []
|
||||
|
||||
traces = []
|
||||
for mf in sorted(d.glob("manifest_*.json")):
|
||||
try:
|
||||
manifest = cls.load_manifest(mf)
|
||||
traces.append(manifest)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
continue
|
||||
return traces
|
||||
97
nexus/traces/bannerlord/REPLAY.md
Normal file
97
nexus/traces/bannerlord/REPLAY.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Bannerlord Session Trace — Replay & Eval Guide
|
||||
|
||||
## Storage Layout
|
||||
|
||||
All traces live under `~/.timmy/traces/bannerlord/`:
|
||||
|
||||
```
|
||||
~/.timmy/traces/bannerlord/
|
||||
trace_<trace_id>.jsonl # One line per ODA cycle (full state + actions)
|
||||
manifest_<trace_id>.json # Session metadata, counts, replay command
|
||||
```
|
||||
|
||||
## Trace Format (JSONL)
|
||||
|
||||
Each line is one ODA cycle:
|
||||
|
||||
```json
|
||||
{
|
||||
"cycle_index": 0,
|
||||
"timestamp_start": "2026-04-10T20:15:00+00:00",
|
||||
"timestamp_end": "2026-04-10T20:15:45+00:00",
|
||||
"duration_ms": 45000,
|
||||
|
||||
"screenshot_path": "/tmp/bannerlord_capture_1744320900.png",
|
||||
"window_found": true,
|
||||
"screen_size": [1920, 1080],
|
||||
"mouse_position": [960, 540],
|
||||
"playtime_hours": 142.5,
|
||||
"players_online": 8421,
|
||||
"is_running": true,
|
||||
|
||||
"actions_planned": [{"type": "move_to", "x": 960, "y": 540}],
|
||||
"actions_executed": [{"success": true, "action": "move_to", ...}],
|
||||
"actions_succeeded": 1,
|
||||
"actions_failed": 0,
|
||||
|
||||
"hermes_session_id": "f47ac10b",
|
||||
"hermes_log_id": "",
|
||||
"harness_session_id": "f47ac10b"
|
||||
}
|
||||
```
|
||||
|
||||
## Capturing a Trace
|
||||
|
||||
```bash
|
||||
# Run harness with trace logging enabled
|
||||
cd /path/to/the-nexus
|
||||
python -m nexus.bannerlord_harness --mock --trace --iterations 3
|
||||
```
|
||||
|
||||
The trace and manifest are written to `~/.timmy/traces/bannerlord/` on harness shutdown.
|
||||
|
||||
## Replay Protocol
|
||||
|
||||
1. Load a trace: `BannerlordTraceLogger.load_trace(trace_file)`
|
||||
2. Create a fresh harness in mock mode
|
||||
3. For each cycle in the trace:
|
||||
- Re-execute the `actions_planned` list
|
||||
- Compare actual `actions_executed` outcomes against the recorded ones
|
||||
4. Score: `(matching_actions / total_actions) * 100`
|
||||
|
||||
### Eval Criteria
|
||||
|
||||
| Score | Grade | Meaning |
|
||||
|---------|----------|--------------------------------------------|
|
||||
| >= 90% | PASS | Replay matches original closely |
|
||||
| 70-89% | PARTIAL | Some divergence, investigate differences |
|
||||
| < 70% | FAIL | Significant drift, review action semantics |
|
||||
|
||||
## Replay Script (sketch)
|
||||
|
||||
```python
|
||||
from nexus.bannerlord_trace import BannerlordTraceLogger
|
||||
from nexus.bannerlord_harness import BannerlordHarness
|
||||
|
||||
# Load trace
|
||||
cycles = BannerlordTraceLogger.load_trace(
|
||||
Path.home() / ".timmy" / "traces" / "bannerlord" / "trace_bl_xxx.jsonl"
|
||||
)
|
||||
|
||||
# Replay
|
||||
harness = BannerlordHarness(enable_mock=True, enable_trace=False)
|
||||
await harness.start()
|
||||
|
||||
for cycle in cycles:
|
||||
for action in cycle["actions_planned"]:
|
||||
result = await harness.execute_action(action)
|
||||
# Compare result against cycle["actions_executed"]
|
||||
|
||||
await harness.stop()
|
||||
```
|
||||
|
||||
## Hermes Session Mapping
|
||||
|
||||
The `hermes_session_id` and `hermes_log_id` fields link traces to Hermes session logs.
|
||||
When a trace is captured during a live Hermes session, populate these fields so
|
||||
the trace can be correlated with the broader agent conversation context.
|
||||
18
nexus/traces/bannerlord/sample_manifest.json
Normal file
18
nexus/traces/bannerlord/sample_manifest.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"trace_id": "bl_20260410_201500_a1b2c3",
|
||||
"harness_session_id": "f47ac10b",
|
||||
"hermes_session_id": "f47ac10b",
|
||||
"hermes_log_id": "",
|
||||
"game": "Mount & Blade II: Bannerlord",
|
||||
"app_id": 261550,
|
||||
"started_at": "2026-04-10T20:15:00+00:00",
|
||||
"finished_at": "2026-04-10T20:17:30+00:00",
|
||||
"total_cycles": 3,
|
||||
"total_actions": 6,
|
||||
"total_succeeded": 6,
|
||||
"total_failed": 0,
|
||||
"trace_file": "~/.timmy/traces/bannerlord/trace_bl_20260410_201500_a1b2c3.jsonl",
|
||||
"trace_dir": "~/.timmy/traces/bannerlord",
|
||||
"replay_command": "python -m nexus.bannerlord_harness --mock --replay ~/.timmy/traces/bannerlord/trace_bl_20260410_201500_a1b2c3.jsonl",
|
||||
"eval_note": "To replay: load trace, re-execute each cycle's actions_planned against a fresh harness in mock mode, compare actions_executed outcomes. Success metric: >=90% action parity between original and replay runs."
|
||||
}
|
||||
3
nexus/traces/bannerlord/sample_trace.jsonl
Normal file
3
nexus/traces/bannerlord/sample_trace.jsonl
Normal file
@@ -0,0 +1,3 @@
|
||||
{"cycle_index": 0, "timestamp_start": "2026-04-10T20:15:00+00:00", "timestamp_end": "2026-04-10T20:15:45+00:00", "duration_ms": 45000, "screenshot_path": "/tmp/bannerlord_capture_1744320900.png", "window_found": true, "screen_size": [1920, 1080], "mouse_position": [960, 540], "playtime_hours": 142.5, "players_online": 8421, "is_running": true, "actions_planned": [{"type": "move_to", "x": 960, "y": 540}, {"type": "press_key", "key": "space"}], "decision_note": "Initial state capture. Move to screen center and press space to advance.", "actions_executed": [{"success": true, "action": "move_to", "params": {"type": "move_to", "x": 960, "y": 540}, "timestamp": "2026-04-10T20:15:30+00:00", "error": null}, {"success": true, "action": "press_key", "params": {"type": "press_key", "key": "space"}, "timestamp": "2026-04-10T20:15:45+00:00", "error": null}], "actions_succeeded": 2, "actions_failed": 0, "hermes_session_id": "f47ac10b", "hermes_log_id": "", "harness_session_id": "f47ac10b"}
|
||||
{"cycle_index": 1, "timestamp_start": "2026-04-10T20:15:45+00:00", "timestamp_end": "2026-04-10T20:16:30+00:00", "duration_ms": 45000, "screenshot_path": "/tmp/bannerlord_capture_1744320945.png", "window_found": true, "screen_size": [1920, 1080], "mouse_position": [960, 540], "playtime_hours": 142.5, "players_online": 8421, "is_running": true, "actions_planned": [{"type": "press_key", "key": "p"}], "decision_note": "Open party screen to inspect troops.", "actions_executed": [{"success": true, "action": "press_key", "params": {"type": "press_key", "key": "p"}, "timestamp": "2026-04-10T20:16:00+00:00", "error": null}], "actions_succeeded": 1, "actions_failed": 0, "hermes_session_id": "f47ac10b", "hermes_log_id": "", "harness_session_id": "f47ac10b"}
|
||||
{"cycle_index": 2, "timestamp_start": "2026-04-10T20:16:30+00:00", "timestamp_end": "2026-04-10T20:17:30+00:00", "duration_ms": 60000, "screenshot_path": "/tmp/bannerlord_capture_1744321020.png", "window_found": true, "screen_size": [1920, 1080], "mouse_position": [960, 540], "playtime_hours": 142.5, "players_online": 8421, "is_running": true, "actions_planned": [{"type": "press_key", "key": "escape"}, {"type": "move_to", "x": 500, "y": 300}, {"type": "click", "x": 500, "y": 300}], "decision_note": "Close party screen, click on campaign map settlement.", "actions_executed": [{"success": true, "action": "press_key", "params": {"type": "press_key", "key": "escape"}, "timestamp": "2026-04-10T20:16:45+00:00", "error": null}, {"success": true, "action": "move_to", "params": {"type": "move_to", "x": 500, "y": 300}, "timestamp": "2026-04-10T20:17:00+00:00", "error": null}, {"success": true, "action": "click", "params": {"type": "click", "x": 500, "y": 300}, "timestamp": "2026-04-10T20:17:30+00:00", "error": null}], "actions_succeeded": 3, "actions_failed": 0, "hermes_session_id": "f47ac10b", "hermes_log_id": "", "harness_session_id": "f47ac10b"}
|
||||
Reference in New Issue
Block a user