feat: expand portal registry schema (#718)
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s

This commit was merged in pull request #718.
This commit is contained in:
2026-03-28 17:01:49 +00:00
parent 68801c4813
commit 8313533304
3 changed files with 98 additions and 4 deletions

View File

@@ -102,22 +102,47 @@ A portal is a game configuration. To add one:
"name": "New Game", "name": "New Game",
"description": "What this portal is.", "description": "What this portal is.",
"status": "offline", "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, "app_id": 12345,
"window_title": "New Game Window Title", "window_title": "New Game Window Title",
"destination": { "destination": {
"type": "harness", "type": "harness",
"action_label": "Enter New Game",
"params": { "world": "new-world" } "params": { "world": "new-world" }
} }
} }
``` ```
2. **No code changes.** The heartbeat loop reads `portals.json`, Required metadata fields:
uses `app_id` for Steam API calls and `window_title` for - `portal_type` — high-level kind (`game-world`, `operator-room`, `research-space`, `experiment`)
screenshot targeting. The MCP tools are game-agnostic. - `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` 3. **Game-specific prompts** go in `training/data/prompts_*.yaml`
to teach the model what the game looks like and how to play it. 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) ## Portal: Bannerlord (Primary)
**Steam App ID:** `261550` **Steam App ID:** `261550`

View File

@@ -4,12 +4,20 @@
"name": "Morrowind", "name": "Morrowind",
"description": "The Vvardenfell harness. Ash storms and ancient mysteries.", "description": "The Vvardenfell harness. Ash storms and ancient mysteries.",
"status": "online", "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", "color": "#ff6600",
"position": { "x": 15, "y": 0, "z": -10 }, "position": { "x": 15, "y": 0, "z": -10 },
"rotation": { "y": -0.5 }, "rotation": { "y": -0.5 },
"destination": { "destination": {
"url": "https://morrowind.timmy.foundation", "url": "https://morrowind.timmy.foundation",
"type": "harness", "type": "harness",
"action_label": "Enter Vvardenfell",
"params": { "world": "vvardenfell" } "params": { "world": "vvardenfell" }
} }
}, },
@@ -17,13 +25,21 @@
"id": "bannerlord", "id": "bannerlord",
"name": "Bannerlord", "name": "Bannerlord",
"description": "Calradia battle harness. Massive armies, tactical command.", "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", "color": "#ffd700",
"position": { "x": -15, "y": 0, "z": -10 }, "position": { "x": -15, "y": 0, "z": -10 },
"rotation": { "y": 0.5 }, "rotation": { "y": 0.5 },
"destination": { "destination": {
"url": "https://bannerlord.timmy.foundation", "url": "https://bannerlord.timmy.foundation",
"type": "harness", "type": "harness",
"action_label": "Enter Calradia",
"params": { "world": "calradia" } "params": { "world": "calradia" }
} }
}, },
@@ -32,12 +48,20 @@
"name": "Workshop", "name": "Workshop",
"description": "The creative harness. Build, script, and manifest.", "description": "The creative harness. Build, script, and manifest.",
"status": "online", "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", "color": "#4af0c0",
"position": { "x": 0, "y": 0, "z": -20 }, "position": { "x": 0, "y": 0, "z": -20 },
"rotation": { "y": 0 }, "rotation": { "y": 0 },
"destination": { "destination": {
"url": "https://workshop.timmy.foundation", "url": "https://workshop.timmy.foundation",
"type": "harness", "type": "harness",
"action_label": "Enter Workshop",
"params": { "mode": "creative" } "params": { "mode": "creative" }
} }
} }

View File

@@ -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