docs/adr/0175-rivals-agent-is-a-gameplay-skill.md

ADR 0175: Rivals Agent is a gameplay skill

Status: accepted (James, 2026-09-20). Amended 2026-10-17 (VUH-2419): the Clankie side moved out of core into an external game extension; see the amendment below.

Decision

Clankie uses Rivals Agent for both Spider-Man tactics and controller execution. Clankie owns the sitting's purpose, conversation, and what he remembers. The Rivals process owns perception, tactical decisions, and the fast guarded pad loop. The Pokémon body seam remains specific to Pokémon.

Contract

POST /v1/rivals and the captain's rivals tool share one validated command schema. Commands are status, start, objective, observe, share, and stop. CLI and TUI use the same operator API. The service resolves the rivals-agent bearer from its broker and reads gameplay.rivalsUrl live. Redirects are refused; response bodies are bounded.

The server runs in the PC's interactive desktop session. It opens capture and the pad only when a sitting starts. A start has an idempotency key; subsequent commands name the observed session ID, so a delayed stop cannot kill a new sitting. Starts return starting; only a guarded loop tick establishes running. Each sitting is bounded to 1–1800 seconds (default 300), and stop requests return stopping until the loop releases its pad. The existing practice-range guards remain authoritative. This API does not navigate menus, queue matches, or expose raw inputs or a shell.

Objectives have mode and a prose note. autonomous uses the scripted tactical policy, combat practices ordinary engagement while preserving its retreat guard, and disengage asks the controller to withdraw. Notes are retained context; noteApplied: false states that the current scripted policy does not interpret them. This is not a claim that a learned policy is installed.

The observer publishes only frames whose range HUD is confirmed. Missing or older-than-one-second frames refuse. PNG encoding happens on the reader thread, outside the control loop. A read-only watch capability names one sitting and expires when it stops; it cannot call control endpoints. Go Live consumes that same frame source through the existing PNG publisher. A publish request is requested, not live; the existing Vox media receipt proves delivery. There is no game-audio feed in this integration.

Alternatives and limits

Generalizing the GBA action schema would mix battle menus and real-time tactics in one contract. A separate small client preserves both domains. Routing each stick input through Clankie's model would put network/model latency in the reflex loop; high-level objectives avoid that.

The bridge's --dry mode runs recorded pixels through real perception, policy, and controller with a fake pad. It reports execution: replay; it verifies the integration, not closed-loop gameplay skill. Live PC and Discord delivery need their own evidence. Only one operator may own the PC desktop at a time, including other Rivals development scripts. See setup and VUH-1316.

Amendment: Rivals is an external game extension (James, 2026-10-17, VUH-2419)

James moved Rivals out of Clankie's core. The client, command schema, sitting service, rivals tool, POST /v1/rivals route and gameplay skill now live in the Rivals Agent repository under clankie-extension/, beside the session server they drive. Core keeps no Rivals code, settings, credentials wiring, CLI/TUI commands or skill: gameplay.rivalsUrl is retired (dropped on load), clankie rivals and /rivals are gone, and docs/rivals.md is a pointer.

The boundary above is unchanged. Clankie still owns the sitting's purpose, conversation and memory; Rivals Agent owns perception, tactics and the guarded pad loop. The extension keeps the same contract, exact-stop rules and receipt recovery, and its fault-injection suite runs against a real Clankie core.

It implements the public module contract that ADR 0234's amendment adds to @clankie/game-extension: the owner installs it with clankie games extensions add MODULE url=ORIGIN, which records the module path in gameplay.extensions; the service loads it at start and hands it the durable play ledger, lifecycle wrapper, its own broker credentials (rivals-agent), a state directory and the Go Live port. Re-enabling under VUH-1325 therefore needs only installing or registering the extension, never a core change. The bridge stays disabled; installing it authorizes no sitting.

The old core record <state>/body/rivals-session.json is not migrated; the extension keeps its own under <state>/game-extensions/rivals/. When it moved, the owner's state root had no such record and no play claim (checked 2026-10-17), so nothing was held across the move.