From 8313533304c0267319eeb1b46f06f8c3c3e25d87 Mon Sep 17 00:00:00 2001 From: Timmy Time Date: Sat, 28 Mar 2026 17:01:49 +0000 Subject: [PATCH] feat: expand portal registry schema (#718) --- GAMEPORTAL_PROTOCOL.md | 31 +++++++++++++++++-- portals.json | 26 +++++++++++++++- tests/test_portal_registry_schema.py | 45 ++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 tests/test_portal_registry_schema.py diff --git a/GAMEPORTAL_PROTOCOL.md b/GAMEPORTAL_PROTOCOL.md index 87f2589..bc59d87 100644 --- a/GAMEPORTAL_PROTOCOL.md +++ b/GAMEPORTAL_PROTOCOL.md @@ -102,22 +102,47 @@ A portal is a game configuration. To add one: "name": "New Game", "description": "What this portal is.", "status": "offline", + "portal_type": "game-world", + "world_category": "rpg", + "environment": "staging", + "access_mode": "operator", + "readiness_state": "prototype", + "telemetry_source": "hermes-harness:new-game-bridge", + "owner": "Timmy", "app_id": 12345, "window_title": "New Game Window Title", "destination": { "type": "harness", + "action_label": "Enter New Game", "params": { "world": "new-world" } } } ``` -2. **No code changes.** The heartbeat loop reads `portals.json`, - uses `app_id` for Steam API calls and `window_title` for - screenshot targeting. The MCP tools are game-agnostic. +Required metadata fields: +- `portal_type` — high-level kind (`game-world`, `operator-room`, `research-space`, `experiment`) +- `world_category` — subtype for navigation and grouping (`rpg`, `workspace`, `sim`, etc.) +- `environment` — `production`, `staging`, or `local` +- `access_mode` — `public`, `operator`, or `local-only` +- `readiness_state` — `playable`, `active`, `prototype`, `rebuilding`, `blocked`, `offline` +- `telemetry_source` — where truth/status comes from +- `owner` — who currently owns the world or integration lane +- `destination.action_label` — human-facing action text for UI cards/directories + +2. **No mandatory game-specific code changes.** The heartbeat loop reads `portals.json`, + uses metadata for grouping/status/visibility, and can still use fields like + `app_id` and `window_title` for screenshot targeting where relevant. The MCP tools remain game-agnostic. 3. **Game-specific prompts** go in `training/data/prompts_*.yaml` to teach the model what the game looks like and how to play it. +4. **Migration from legacy portal definitions** +- old portal entries with only `id`, `name`, `description`, `status`, and `destination` + should be upgraded in place +- preserve visual fields like `color`, `position`, and `rotation` +- add the new metadata fields so the same registry can drive future atlas, status wall, + preview cards, and many-portal navigation without inventing parallel registries + ## Portal: Bannerlord (Primary) **Steam App ID:** `261550` diff --git a/portals.json b/portals.json index f319cf0..c57e201 100644 --- a/portals.json +++ b/portals.json @@ -4,12 +4,20 @@ "name": "Morrowind", "description": "The Vvardenfell harness. Ash storms and ancient mysteries.", "status": "online", + "portal_type": "game-world", + "world_category": "rpg", + "environment": "production", + "access_mode": "public", + "readiness_state": "playable", + "telemetry_source": "hermes-harness:morrowind-mcp", + "owner": "Timmy", "color": "#ff6600", "position": { "x": 15, "y": 0, "z": -10 }, "rotation": { "y": -0.5 }, "destination": { "url": "https://morrowind.timmy.foundation", "type": "harness", + "action_label": "Enter Vvardenfell", "params": { "world": "vvardenfell" } } }, @@ -17,13 +25,21 @@ "id": "bannerlord", "name": "Bannerlord", "description": "Calradia battle harness. Massive armies, tactical command.", - "status": "online", + "status": "rebuilding", + "portal_type": "game-world", + "world_category": "strategy-rpg", + "environment": "staging", + "access_mode": "operator", + "readiness_state": "prototype", + "telemetry_source": "hermes-harness:bannerlord-bridge", + "owner": "Timmy", "color": "#ffd700", "position": { "x": -15, "y": 0, "z": -10 }, "rotation": { "y": 0.5 }, "destination": { "url": "https://bannerlord.timmy.foundation", "type": "harness", + "action_label": "Enter Calradia", "params": { "world": "calradia" } } }, @@ -32,12 +48,20 @@ "name": "Workshop", "description": "The creative harness. Build, script, and manifest.", "status": "online", + "portal_type": "operator-room", + "world_category": "workspace", + "environment": "local", + "access_mode": "local-only", + "readiness_state": "active", + "telemetry_source": "hermes-harness:workshop-state", + "owner": "Alexander", "color": "#4af0c0", "position": { "x": 0, "y": 0, "z": -20 }, "rotation": { "y": 0 }, "destination": { "url": "https://workshop.timmy.foundation", "type": "harness", + "action_label": "Enter Workshop", "params": { "mode": "creative" } } } diff --git a/tests/test_portal_registry_schema.py b/tests/test_portal_registry_schema.py new file mode 100644 index 0000000..062f6c6 --- /dev/null +++ b/tests/test_portal_registry_schema.py @@ -0,0 +1,45 @@ +import json +from pathlib import Path + + +REQUIRED_TOP_LEVEL_KEYS = { + "id", + "name", + "description", + "status", + "portal_type", + "world_category", + "environment", + "access_mode", + "readiness_state", + "telemetry_source", + "owner", + "destination", +} + +REQUIRED_DESTINATION_KEYS = {"type", "action_label"} + + +def test_portals_json_uses_expanded_registry_schema() -> None: + portals = json.loads(Path("portals.json").read_text()) + + assert portals, "portals.json should define at least one portal" + for portal in portals: + assert REQUIRED_TOP_LEVEL_KEYS.issubset(portal.keys()) + assert REQUIRED_DESTINATION_KEYS.issubset(portal["destination"].keys()) + + +def test_gameportal_protocol_documents_new_metadata_fields_and_migration() -> None: + protocol = Path("GAMEPORTAL_PROTOCOL.md").read_text() + + for term in [ + "portal_type", + "world_category", + "environment", + "access_mode", + "readiness_state", + "telemetry_source", + "owner", + "Migration from legacy portal definitions", + ]: + assert term in protocol