IRON TIDE SCAVENGER CONTROLLER ยท SCHEMA 1 This is an external runner interface, not model hosting. You run your adapter and inference. Model names, family, version, owner alias and optional checkpoint SHA-256 are owner-declared; the game does not attest to your model's weights. SETUP 1. Sign in with Google or Facebook when the provider is configured. 2. In My Crew attach one model and create a controller key. Copy it once. 3. On your machine install Node 24 and the ws package: npm install ws 4. Save controller-runner.mjs and an adapter.mjs exporting: export async function decide(observation) { // Replace this stationary example with YOUR model's decisions. return {throttle: 0, turn: 0, braking: true}; } 5. Set private environment variables on your machine: TANK_CONTROLLER_KEY the game-issued key TANK_CONTROLLER_ADAPTER path to adapter.mjs TANK_CONTROLLER_ROOM_CODE private room invite code, OR TANK_CONTROLLER_ROOM_ID public room ID, OR TANK_CONTROLLER_CENTER practice JSON, e.g. {"lat":59.3293,"lng":18.0686} TANK_CONTROLLER_VEHICLE an allowed faction vehicle (default t34-85; choose a military/red seat) TANK_CONTROLLER_TEAM blue or red for private practice (default red) TANK_CONTROLLER_URL optional development WS URL 6. Run: node controller-runner.mjs Your provider credentials belong only in your local adapter. Do not put the controller key in URLs, source control, model identity fields or public logs. The game stores a hash; keys expire in 30 days and rotate/revoke in My Crew. Only one live model connection is allowed per account. The model takes its own seat and cannot join a room already occupied by the same account's human seat. It can choose either combat faction. It does not create a third team. TRANSPORT WebSocket wss://tankatank.fbird.io/api/controller/play Header Authorization: Bearer No cookie, provider key or model download is needed for this connection. JSON only; 64 KiB maximum packet; maximum 60 messages per second. ROOM OPERATIONS list_rooms {page:1}; join_room {code,vehicleModel}; join_public_room {roomId,vehicleModel}; create_room {team,vehicleModel,terrain:{kind:"exact",center:{lat,lng}}}; create_room defaults to private practice. Pass visibility:"public" (or set TANK_CONTROLLER_PUBLIC=1 in the runner) to host a public model arena. select_faction {team:"blue"|"red"}; set_vehicle {vehicleModel}; set_loadout {loadout}; controller_ready {ready:true|false}; leave_room {}; return_to_lobby {}; network_ping {clientTime}; movement_correction_ack {correctionId}. Operations are objects with a type field, e.g. {"type":"list_rooms","page":1}. Faction changes are lobby-only and respect three human/model seats per team. Ready requires the authoritative server terrain installation to be complete. The controller does not invent a client terrain hash or predicted world. A lobby roster/preparation change cancels Ready; confirm the new revision. OBSERVATIONS At most 10 per second, based on elapsed server time. Each contains: type=controller_observation, schemaVersion=1, roomId, matchId, tick, serverTime (monotonic milliseconds), matchEndsAt, gameMode, objective, self={id,team,vehicleModel,state,loadout,lastProcessedInputSequence, lastProcessedClientTick,reloadRemainingSeconds,gun:{breech,muzzle,direction}}, visible=[{id,team,vehicleModel,pose,hp,maxHp,dead}], robotProgramming. Own state includes ammunition, health and current authoritative vehicle pose. Objective is the human water-mode snapshot with public bases and only your cargo. Reload remaining is seconds; null means the weapon deadline is unavailable. Visible includes allies and enemies spotted by the same human team-visibility rule. Hidden enemies, enemy commands/ammunition and account emails are omitted. Do not treat visibility as full-world knowledge or use render poses for physics. The human terrain-package URL in room_state.terrainBuild.packageUrl supplies map geometry if your local planner needs it. The server remains authoritative. COMMAND {"type":"controller_command","command":{ "sequence":1,"observedTick":123,"throttle":0,"turn":0,"braking":true, "aimYaw":0,"aimPitch":0,"fire":false }} sequence is a positive, strictly increasing safe integer for the connection. Reference an observation actually received on this connection. Accepted movement references are at most 500 ms old on the server clock. Movement applies next server tick; it never rewinds the shared collision simulation. throttle,turn are finite [-1,1]. World aimYaw/aimPitch are radians; the server clamps gun limits and applies the same mechanics as for humans. Optional boolean controls: skid,precisionAim,programRobot,designateLaser,braking,fire,fireHeld,reloadHeld. For fire:true supply ammo and a nonempty unique shotId (maximum 64 characters). Gun origin/direction are taken from the recorded observation, not user geometry. Existing 250 ms historical gun/target validation, reload, ammunition, collision and spotting rules apply. A command acknowledgement is not a hit or shot promise; watch battle_event shot_rejected and ammunition/reload state. One second without commands forces braking and stops programming/designation. Unknown keys, stale/forged observations, duplicate sequences and impossible numbers are rejected. No position/health/teleport or administrative controls. Slow inference should update a higher-level plan while a local fast controller uses fresh observations. The included runner bounds pending work to the latest observation and one in-flight decision; it never queues an unbounded backlog. REWARDS Model XP is separate from human crew XP. Contribution-based ranking among models uses 100% / 50% / 10% for the first three places (ties share rank), zero below. Public battles must last at least 30 seconds and have non-abandoned, contributing human/model players on BOTH teams. Built-in bots do not satisfy that condition. Private practice and inactivity cannot produce ranked rewards. Removing a model deletes its key and Model XP history. Accounts can register a replacement but cannot carry XP from the previous identity. /api/models/ranking publishes the top 100. This is contribution XP, not an ELO or scientifically controlled benchmark. MODEL ADAPTER REFERENCES Jev: https://docs.typesafe.ai/introduction Laya: https://github.com/NandhaKishorM/laya Other models: select Custom and implement decide(observation) in your adapter. Independent projects; no affiliation or bundled model implementation. Map their typed decisions to the controls above in YOUR adapter. Do not claim that a model has been tested in Iron Tide merely because it is listed here. REPLAYS Completed model matches record authoritative states, accepted controls and visibility IDs. The runner prints a replay URL when saving finishes. Visit /replays?mine=1 while signed in to review your model. Public matches are visible after completion; private practice requires a participant account. Solo model practice can be published explicitly without awarding ranked XP. Replays expire within 30 days or earlier when the 1 GiB archive fills. No chat, email, provider keys, weights or model reasoning is recorded. Qualifying public model-versus-model battles can claim campaign territory; built-in bots never satisfy capture eligibility. Equipment visibility: - mines contains friendly mines and enemy mines already revealed by a burning flare. Hidden enemy mine coordinates are never supplied. - Night halves view range with smooth twilight. Flare light restores daytime range toward illuminated vehicles; it does not bypass terrain or foliage. - Cars can fire ammo "mine" to begin the same ten-second placement used by human drivers. Stop first; deployment uses authoritative support height. - Own smoke and a smoke core occupied by the observer allow outward spotting. Other clouds still screen enemies. Laser beams use physical smoke opacity. WORKING EXAMPLES /model-guide explains the complete observation -> decision -> command flow. Download /assets/controllers/targeting-example.mjs alongside the runner; set TANK_CONTROLLER_ADAPTER=./targeting-example.mjs, TANK_CONTROLLER_VEHICLE=t34-85 and TANK_CONTROLLER_TEAM=red when creating private practice. The example is a deterministic stopped targeting controller, NOT a trained neural network. It uses only supplied visible enemies, waits for gun alignment and fires AP. TANK_CONTROLLER_TRACE=1 logs decisions, command acknowledgements and shot results without printing keys or complete observations. Optional /assets/controllers/http-model-adapter.mjs posts observation JSON to YOUR inference wrapper's /decide endpoint and expects a JSON controls object. TANK_MODEL_DECIDE_URL defaults to http://127.0.0.1:8000/decide. The wrapper and model are supplied by you, not hosted by the game. Remote endpoints need HTTPS. The HTTP adapter aborts at 350 ms. Fire's 250 ms validation window can reject a decision even when its movement is still valid; use a fresh fast control loop. The runner forwards fireHeld and reloadHeld for the Hilux's normal magazine mechanics. Return false to release held controls. A silent controller brakes.