From b3bada07124288699aae18464a2522888b19cca8 Mon Sep 17 00:00:00 2001 From: Natalie Date: Sun, 10 May 2026 18:08:46 -0700 Subject: [PATCH] =?UTF-8?q?fix(tooling):=20=F0=9F=94=A5=20remove=20claude-?= =?UTF-8?q?player=20adapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- tooling/claude-player/README.md | 57 ------ tooling/claude-player/package.json | 25 --- tooling/claude-player/src/agent.ts | 296 --------------------------- tooling/claude-player/src/harness.ts | 198 ------------------ tooling/claude-player/src/index.ts | 99 --------- tooling/claude-player/src/types.ts | 155 -------------- tooling/claude-player/tsconfig.json | 26 --- 7 files changed, 856 deletions(-) delete mode 100644 tooling/claude-player/README.md delete mode 100644 tooling/claude-player/package.json delete mode 100644 tooling/claude-player/src/agent.ts delete mode 100644 tooling/claude-player/src/harness.ts delete mode 100644 tooling/claude-player/src/index.ts delete mode 100644 tooling/claude-player/src/types.ts delete mode 100644 tooling/claude-player/tsconfig.json diff --git a/tooling/claude-player/README.md b/tooling/claude-player/README.md deleted file mode 100644 index b0106999..00000000 --- a/tooling/claude-player/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# @magic-civ/claude-player - -Claude Agent SDK adapter for the Magic Civilization Claude Player API -(p2-67 Phase 4). Drives one player slot in a headless game while the -production AI controls the others. - -## Run - -```bash -cd tooling/claude-player -npm install -export ANTHROPIC_API_KEY=sk-ant-... -npm run dev # tsx — no build needed -# or -npm run build && npm start # compiled JS -``` - -Each run writes append-only logs to `.local/runs//`: -- `log.jsonl` — agent decisions + notifications + run metadata. -- `wire.jsonl` — raw JSON-Lines between adapter and harness. -- `result.json` — final result (`turns_played`, `reason`, `final_view`). - -## Env vars - -| Var | Default | Purpose | -|---|---|---| -| `ANTHROPIC_API_KEY` | *(required)* | Claude API credentials | -| `CP_MODEL` | `claude-opus-4-7` | Model id | -| `CP_MAX_TURNS` | `100` | Hard turn cap | -| `CP_MAX_IDLE_ENDS` | `3` | Consecutive idle end_turns before bail | -| `CP_SERVER` | `/scripts/claude-player-server.sh` | Harness launcher | -| `CP_SEED` / `CP_PLAYERS` / `CP_CLAUDE_SLOT` / `CP_OMNISCIENT` / `CP_TIMEOUT_SEC` | see CLAUDE_PLAYER_API.md | Forwarded to harness | - -## Architecture - -``` -@magic-civ/claude-player (Node) - │ - ├── HarnessClient — spawn() + JSON-Lines pump - │ - └── runAgent — Anthropic Messages API tool-use loop - ├── tool: view() - ├── tool: act(action) - └── tool: end_turn() - │ - ▼ stdin/stdout -scripts/claude-player-server.sh - │ - ▼ -flatpak Godot --headless res://engine/scenes/headless/claude_player_main.tscn - │ - ▼ -GdPlayerApi → mc-player-api → mc-turn::action_handlers -``` - -Wire-protocol contract: `src/game/engine/docs/CLAUDE_PLAYER_API.md`. -Owning objective: `.project/objectives/p2-67-claude-player-api.md`. diff --git a/tooling/claude-player/package.json b/tooling/claude-player/package.json deleted file mode 100644 index 086fa3e1..00000000 --- a/tooling/claude-player/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@magic-civ/claude-player", - "version": "0.1.0", - "private": true, - "type": "module", - "description": "Claude Agent SDK adapter for the Magic Civilization Claude Player API (p2-67 Phase 4).", - "main": "dist/index.js", - "scripts": { - "build": "tsc -p tsconfig.json", - "start": "node --enable-source-maps dist/index.js", - "dev": "tsx src/index.ts", - "typecheck": "tsc -p tsconfig.json --noEmit" - }, - "dependencies": { - "@anthropic-ai/sdk": "^0.40.0" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.0", - "typescript": "^5.6.0" - }, - "engines": { - "node": ">=20" - } -} diff --git a/tooling/claude-player/src/agent.ts b/tooling/claude-player/src/agent.ts deleted file mode 100644 index 3021464a..00000000 --- a/tooling/claude-player/src/agent.ts +++ /dev/null @@ -1,296 +0,0 @@ -// Claude Agent SDK loop driving the harness. -// -// Three tools are exposed to Claude: -// view() → returns the current PlayerView JSON -// act(action) → applies one PlayerAction, returns events + new view -// end_turn() → sugar for act({type:"end_turn"}) -// -// The loop runs until a `game_over` notification fires, a `turn` field in -// the view exceeds `maxTurns`, or the model emits `end_turn` `maxIdleEndTurns` -// times in a row without any other action (defensive cap). - -import Anthropic from "@anthropic-ai/sdk"; - -import type { HarnessClient } from "./harness.js"; -import type { PlayerAction, PlayerView, Response } from "./types.js"; - -export interface AgentOptions { - /** Live HarnessClient. */ - harness: HarnessClient; - /** Anthropic API client. */ - anthropic: Anthropic; - /** Model id (claude-opus-4-7, claude-sonnet-4-6, ...). */ - model: string; - /** Hard cap on turns played. */ - maxTurns: number; - /** Defensive cap on consecutive `end_turn` calls without state change. */ - maxIdleEndTurns: number; - /** Append-only log sink (one JSON entry per agent step). */ - onLog?: (entry: Record) => void; -} - -const SYSTEM_PROMPT = `You are playing one player slot in Magic Civilization, -a turn-based 4X strategy game (the "Age of Dwarves" variant — no magic, pure -mundane tech). You are playing against the production AI which controls the -other player slot(s). - -Your goal: maximise your score. Score is dominated by city count, population, -researched tech count, and military strength. Domination victory (controlling -every opponent's original capital) ends the game in your favour immediately. - -You drive the game by calling tools: -- view() — read the current fog-aware game state. -- act(action) — apply one player action. The action JSON shape matches the - PlayerAction enum documented in the game's CLAUDE_PLAYER_API.md. -- end_turn() — end your turn so the AI can take theirs. The harness handles - AI turns automatically before returning control to you. - -Always start your turn with view() to refresh your model of the state. Then -take whichever actions advance your plan. End your turn when you've done all -useful work — don't end early, but don't waste turns on cosmetic actions. - -Per turn, prioritise (in order): -1. Found a city if you have a Founder unit on a viable tile. -2. Train a military unit if you have no army and an enemy is within 5 hexes. -3. Build the next strategic building in your capital. -4. Move units toward their assigned objectives (scouting / attacking / defending). -5. Research the next tech on your path. - -When you finish your turn, call end_turn(). Then read the new state and -take the next turn. The game ends when you call end_turn() and receive a -game_over notification, or when 100 turns have been played.`; - -const TOOLS: Anthropic.Messages.Tool[] = [ - { - name: "view", - description: "Read the current fog-aware game state.", - input_schema: { type: "object", properties: {}, required: [] }, - }, - { - name: "act", - description: - "Take one player action. Returns events and the post-action view.", - input_schema: { - type: "object", - properties: { - action: { - type: "object", - description: - "PlayerAction JSON (e.g. {type:'move',unit_id:'42',to:[5,7]}).", - }, - }, - required: ["action"], - }, - }, - { - name: "end_turn", - description: "End your turn. Sugar for act({type:'end_turn'}).", - input_schema: { type: "object", properties: {}, required: [] }, - }, -]; - -export interface AgentRunResult { - turns_played: number; - reason: "game_over" | "max_turns" | "idle_cap" | "blocker"; - final_view: PlayerView | null; -} - -export async function runAgent(opts: AgentOptions): Promise { - try { - return await runAgentInner(opts); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - opts.onLog?.({ kind: "agent_fatal", error: message }); - return { - turns_played: 0, - reason: "blocker", - final_view: null, - }; - } -} - -async function runAgentInner(opts: AgentOptions): Promise { - const messages: Anthropic.Messages.MessageParam[] = []; - let consecutiveIdleEnds = 0; - let lastTurnNumber = -1; - let finalView: PlayerView | null = null; - - // Seed the conversation with the initial view. - const initialView = await opts.harness.view(); - if (!initialView.ok) { - return { - turns_played: 0, - reason: "blocker", - final_view: null, - }; - } - finalView = initialView.view; - messages.push({ - role: "user", - content: [ - { - type: "text", - text: - "Initial game state (read this before deciding any actions):\n\n" + - JSON.stringify(initialView.view, null, 2), - }, - ], - }); - - for (let step = 0; step < opts.maxTurns * 50; step++) { - if (finalView !== null && finalView.turn > opts.maxTurns) { - return { turns_played: finalView.turn, reason: "max_turns", final_view: finalView }; - } - if (consecutiveIdleEnds >= opts.maxIdleEndTurns) { - return { - turns_played: finalView?.turn ?? 0, - reason: "idle_cap", - final_view: finalView, - }; - } - opts.onLog?.({ step, kind: "agent_request", messages: messages.length }); - const response = await opts.anthropic.messages.create({ - model: opts.model, - max_tokens: 4096, - system: SYSTEM_PROMPT, - tools: TOOLS, - messages, - }); - opts.onLog?.({ step, kind: "agent_response", stop_reason: response.stop_reason }); - messages.push({ role: "assistant", content: response.content }); - - const toolUses = response.content.filter( - (c): c is Anthropic.Messages.ToolUseBlock => c.type === "tool_use", - ); - if (toolUses.length === 0) { - opts.onLog?.({ step, kind: "agent_finished_without_tool" }); - break; - } - - const toolResults: Anthropic.Messages.ToolResultBlockParam[] = []; - let endTurnCalledThisStep = false; - for (const tu of toolUses) { - const result = await runOneTool(opts, tu); - toolResults.push({ - type: "tool_result", - tool_use_id: tu.id, - content: JSON.stringify(result.payload), - }); - if (result.endedTurn) endTurnCalledThisStep = true; - if (result.view !== null) finalView = result.view; - if (result.gameOver) { - opts.onLog?.({ step, kind: "game_over_observed" }); - return { - turns_played: finalView?.turn ?? 0, - reason: "game_over", - final_view: finalView, - }; - } - } - messages.push({ role: "user", content: toolResults }); - - if (endTurnCalledThisStep) { - const newTurn = finalView?.turn ?? -1; - if (newTurn === lastTurnNumber) { - consecutiveIdleEnds++; - } else { - consecutiveIdleEnds = 0; - lastTurnNumber = newTurn; - } - } - } - - return { - turns_played: finalView?.turn ?? 0, - reason: "max_turns", - final_view: finalView, - }; -} - -interface ToolOutcome { - payload: Record; - view: PlayerView | null; - endedTurn: boolean; - gameOver: boolean; -} - -async function runOneTool( - opts: AgentOptions, - tu: Anthropic.Messages.ToolUseBlock, -): Promise { - try { - return await runOneToolInner(opts, tu); - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - return { - payload: { ok: false, error: { code: "internal", message } }, - view: null, - endedTurn: false, - gameOver: false, - }; - } -} - -async function runOneToolInner( - opts: AgentOptions, - tu: Anthropic.Messages.ToolUseBlock, -): Promise { - switch (tu.name) { - case "view": { - const r = await opts.harness.view(); - return outcomeFromResponse(r, false); - } - case "end_turn": { - const r = await opts.harness.endTurn(); - return outcomeFromResponse(r, true); - } - case "act": { - const input = tu.input as { action?: PlayerAction }; - if (!input || typeof input !== "object" || !input.action) { - return { - payload: { - ok: false, - error: { code: "parse_error", message: "act tool requires {action: PlayerAction}" }, - }, - view: null, - endedTurn: false, - gameOver: false, - }; - } - const r = await opts.harness.act(input.action); - const ended = input.action.type === "end_turn"; - return outcomeFromResponse(r, ended); - } - default: - return { - payload: { - ok: false, - error: { code: "parse_error", message: `unknown tool ${tu.name}` }, - }, - view: null, - endedTurn: false, - gameOver: false, - }; - } -} - -function outcomeFromResponse(r: Response, endedTurn: boolean): ToolOutcome { - if (r.ok) { - const events = r.events ?? []; - const gameOver = events.some( - (e) => typeof e === "object" && e !== null && (e as { type?: string }).type === "game_over", - ); - return { - payload: { ok: true, events, view: r.view }, - view: r.view, - endedTurn, - gameOver, - }; - } - return { - payload: { ok: false, error: r.error }, - view: null, - endedTurn: false, - gameOver: false, - }; -} diff --git a/tooling/claude-player/src/harness.ts b/tooling/claude-player/src/harness.ts deleted file mode 100644 index d4497a53..00000000 --- a/tooling/claude-player/src/harness.ts +++ /dev/null @@ -1,198 +0,0 @@ -// JSON-Lines pump over a child process running `scripts/claude-player-server.sh`. -// -// Each request/response/notification is one JSON value per line. Responses are -// correlated by an optional `id` field; the adapter assigns monotonically -// increasing ids so it can route concurrent in-flight requests if it ever -// adds parallelism (single-flight is enough for v1). - -import { spawn, type ChildProcessByStdio } from "node:child_process"; -import { createInterface } from "node:readline"; -import type { Readable, Writable } from "node:stream"; - -import type { - ErrResponse, - Notification, - OkResponse, - PlayerAction, - Response, -} from "./types.js"; - -export interface HarnessOptions { - /** Absolute path to claude-player-server.sh. */ - serverScript: string; - /** Env-var overrides for the harness (CP_SEED, CP_PLAYERS, ...). */ - env?: Record; - /** Hook invoked for every async `Notification` line. */ - onNotification?: (note: Notification) => void; - /** Hook invoked for every line read off stdout, before parsing. */ - onRawLine?: (direction: "<-" | "->", line: string) => void; - /** Default per-request timeout in ms. */ - defaultTimeoutMs?: number; -} - -interface PendingRequest { - id: number; - resolve: (resp: Response) => void; - reject: (err: Error) => void; - timer: NodeJS.Timeout; -} - -/** - * Wraps the harness child process. Exposes `view()`, `act()`, `endTurn()`, - * `shutdown()` — each returns the matching `Response` once it lands. - */ -export class HarnessClient { - private readonly child: ChildProcessByStdio; - private readonly pending = new Map(); - private readonly opts: Required; - private nextId = 1; - private closed = false; - - constructor(opts: HarnessOptions) { - this.opts = { - env: {}, - onNotification: () => {}, - onRawLine: () => {}, - defaultTimeoutMs: 60_000, - ...opts, - }; - this.child = spawn(this.opts.serverScript, [], { - env: { ...process.env, ...this.opts.env }, - stdio: ["pipe", "pipe", "pipe"], - }) as ChildProcessByStdio; - - const out = createInterface({ input: this.child.stdout }); - out.on("line", (line) => { - this.opts.onRawLine("<-", line); - this.handleLine(line); - }); - this.child.stderr.on("data", (chunk: Buffer) => { - // Surface harness stderr to our own stderr so users see crashes / - // engine warnings without them mingling into the protocol channel. - process.stderr.write(chunk); - }); - this.child.on("exit", (code) => { - this.closed = true; - const err = new Error(`harness exited with code ${code ?? "null"}`); - for (const req of this.pending.values()) { - clearTimeout(req.timer); - req.reject(err); - } - this.pending.clear(); - }); - } - - /** Read the current fog-aware view. */ - async view(): Promise { - return this.send({ type: "view" }); - } - - /** Apply one action. Returns the response (ok or err). */ - async act(action: PlayerAction): Promise { - return this.send({ type: "act", action }); - } - - /** Sugar — `act({type:"end_turn"})`. */ - async endTurn(): Promise { - return this.act({ type: "end_turn" }); - } - - /** Tell the harness to exit cleanly. Resolves once the child exits. */ - async shutdown(): Promise { - if (this.closed) return; - try { - await this.send({ type: "shutdown" }); - } catch { - // Child may have exited mid-shutdown — that's the desired terminal - // state, so consume the error and fall through to the exit wait. - } - await new Promise((resolve) => { - if (this.closed) { - resolve(); - return; - } - this.child.once("exit", () => resolve()); - this.child.stdin.end(); - }); - } - - private send(body: Record): Promise { - return new Promise((resolve, reject) => { - if (this.closed) { - reject(new Error("harness already closed")); - return; - } - const id = this.nextId++; - const payload = JSON.stringify({ ...body, id }); - const timer = setTimeout(() => { - this.pending.delete(id); - reject(new Error(`request ${id} (${String(body["type"])}) timed out`)); - }, this.opts.defaultTimeoutMs); - this.pending.set(id, { id, resolve, reject, timer }); - this.opts.onRawLine("->", payload); - this.child.stdin.write(payload + "\n"); - }); - } - - private handleLine(line: string): void { - let parsed: unknown; - try { - parsed = JSON.parse(line); - } catch { - // Non-JSON line on stdout (e.g. engine warnings); ignore. - return; - } - if (typeof parsed !== "object" || parsed === null) return; - const obj = parsed as Record; - - if ("id" in obj && (obj["id"] === null || typeof obj["id"] === "number")) { - const id = obj["id"]; - if (typeof id === "number") { - const pend = this.pending.get(id); - if (pend !== undefined) { - this.pending.delete(id); - clearTimeout(pend.timer); - const response = toResponse(obj); - if (response !== null) { - pend.resolve(response); - } else { - pend.reject(new Error(`malformed response shape: ${line}`)); - } - return; - } - } - } - - // No id → notification. - if (typeof obj["type"] === "string") { - this.opts.onNotification(obj as Notification); - } - } -} - -function toResponse(obj: Record): Response | null { - if (obj["ok"] === true && typeof obj["view"] === "object" && obj["view"] !== null) { - const ok: OkResponse = { - id: (obj["id"] as number | null) ?? null, - ok: true, - view: obj["view"] as OkResponse["view"], - events: Array.isArray(obj["events"]) - ? (obj["events"] as OkResponse["events"]) - : [], - }; - return ok; - } - if (obj["ok"] === false && typeof obj["error"] === "object" && obj["error"] !== null) { - const errObj = obj["error"] as Record; - const err: ErrResponse = { - id: (obj["id"] as number | null) ?? null, - ok: false, - error: { - code: typeof errObj["code"] === "string" ? errObj["code"] : "internal", - message: typeof errObj["message"] === "string" ? errObj["message"] : "", - }, - }; - return err; - } - return null; -} diff --git a/tooling/claude-player/src/index.ts b/tooling/claude-player/src/index.ts deleted file mode 100644 index af597ec5..00000000 --- a/tooling/claude-player/src/index.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Entry point — spawns the harness, wires up the Claude Agent loop, and -// writes an append-only run log under `.local/runs//log.jsonl`. -// See `README.md` for usage and the full env-var contract. - -import Anthropic from "@anthropic-ai/sdk"; -import { mkdir, writeFile } from "node:fs/promises"; -import { createWriteStream } from "node:fs"; -import { dirname, resolve as resolvePath } from "node:path"; -import { fileURLToPath } from "node:url"; - -import { runAgent } from "./agent.js"; -import { HarnessClient } from "./harness.js"; - -const HERE = dirname(fileURLToPath(import.meta.url)); -const PROJECT_ROOT = resolvePath(HERE, "..", "..", ".."); - -function emit(line: string): void { - process.stderr.write(line + "\n"); -} - -async function main(): Promise { - const apiKey = process.env["ANTHROPIC_API_KEY"]; - if (!apiKey || apiKey.length === 0) { - emit("ANTHROPIC_API_KEY not set. Export it before running the adapter."); - process.exitCode = 2; - return; - } - - const serverScript = - process.env["CP_SERVER"] ?? - resolvePath(PROJECT_ROOT, "scripts", "claude-player-server.sh"); - const model = process.env["CP_MODEL"] ?? "claude-opus-4-7"; - const maxTurns = parseInt(process.env["CP_MAX_TURNS"] ?? "100", 10); - const maxIdleEnds = parseInt(process.env["CP_MAX_IDLE_ENDS"] ?? "3", 10); - - const stamp = new Date() - .toISOString() - .replace(/[:.]/g, "-") - .replace("T", "_") - .replace("Z", ""); - const runDir = resolvePath(HERE, "..", ".local", "runs", stamp); - await mkdir(runDir, { recursive: true }); - const logPath = resolvePath(runDir, "log.jsonl"); - const wirePath = resolvePath(runDir, "wire.jsonl"); - const logStream = createWriteStream(logPath, { flags: "a" }); - const wireStream = createWriteStream(wirePath, { flags: "a" }); - const log = (entry: Record): void => { - logStream.write(JSON.stringify({ ts: Date.now(), ...entry }) + "\n"); - }; - - emit(`claude-player run dir: ${runDir}`); - emit(`spawning harness: ${serverScript}`); - - const harness = new HarnessClient({ - serverScript, - env: {}, - onNotification: (note) => { - log({ kind: "notification", ...note }); - }, - onRawLine: (direction, line) => { - wireStream.write(`${direction} ${line}\n`); - }, - }); - - const anthropic = new Anthropic({ apiKey }); - - try { - const result = await runAgent({ - harness, - anthropic, - model, - maxTurns, - maxIdleEndTurns: maxIdleEnds, - onLog: log, - }); - log({ kind: "run_complete", ...result }); - emit( - `run complete — reason=${result.reason}, turns=${result.turns_played}`, - ); - await writeFile( - resolvePath(runDir, "result.json"), - JSON.stringify(result, null, 2), - "utf-8", - ); - } catch (err) { - log({ kind: "fatal", error: err instanceof Error ? err.message : String(err) }); - emit(`fatal: ${err instanceof Error ? err.message : String(err)}`); - process.exitCode = 1; - } finally { - await harness.shutdown(); - logStream.end(); - wireStream.end(); - } -} - -main().catch((err: unknown) => { - emit(`uncaught: ${err instanceof Error ? err.message : String(err)}`); - process.exit(1); -}); diff --git a/tooling/claude-player/src/types.ts b/tooling/claude-player/src/types.ts deleted file mode 100644 index 18835754..00000000 --- a/tooling/claude-player/src/types.ts +++ /dev/null @@ -1,155 +0,0 @@ -// Wire-protocol types — mirror the Rust enums in `mc-player-api`. -// Source of truth: src/game/engine/docs/CLAUDE_PLAYER_API.md. - -/** Hex coordinate carried over the wire — `[col, row]`. */ -export type WireHex = [number, number]; - -/** Stable player slot index, `u8` everywhere in the simulator. */ -export type PlayerId = number; - -/** Discriminated union of every player action variant. */ -export type PlayerAction = - | { type: "end_turn" } - | { type: "noop" } - | { type: "move"; unit_id: string; to: WireHex } - | { type: "attack"; unit_id: string; target: WireHex } - | { type: "ranged_attack"; unit_id: string; target: WireHex } - | { type: "fortify"; unit_id: string } - | { type: "unfortify"; unit_id: string } - | { type: "skip"; unit_id: string } - | { type: "found_city"; unit_id: string } - | { type: "build_improvement"; unit_id: string; improvement_id: string } - | { type: "sentry"; unit_id: string } - | { type: "unsentry"; unit_id: string } - | { type: "issue_patrol"; unit_id: string; waypoints: WireHex[] } - | { type: "cancel_patrol"; unit_id: string } - | { type: "edit_patrol"; unit_id: string; waypoints: WireHex[] } - | { type: "queue_production"; city_id: string; item: string; tile?: WireHex } - | { type: "remove_from_queue"; city_id: string; index: number } - | { type: "queue_reorder"; city_id: string; from: number; to: number } - | { type: "rush_buy"; city_id: string } - | { type: "buy_tile"; city_id: string; tile: WireHex } - | { type: "set_focus"; city_id: string; focus: string } - | { - type: "merge_buildings"; - city_id: string; - building_a: string; - building_b: string; - into: string; - } - | { type: "research_tech"; tech_id: string } - | { type: "research_tradition"; tradition_id: string } - | { type: "declare_war"; on: PlayerId } - | { type: "offer_peace"; to: PlayerId }; - -/** One entry in a unit's or empire's `legal_actions` list. */ -export interface LegalActionEntry { - action: PlayerAction; - enabled: boolean; - disabled_reason?: string; -} - -export interface UnitView { - id: string; - type: string; - position: WireHex; - owner: PlayerId; - hp: number; - max_hp: number; - movement_left: number; - movement_max: number; - experience: number; - promotion_available: boolean; - fortified: boolean; - sentry: boolean; - legal_actions?: LegalActionEntry[]; -} - -export interface CityView { - id: string; - name: string; - position: WireHex; - owner: PlayerId; - is_capital: boolean; - population: number; - food_stored: number; - food_growth_threshold: number; - production_queue: Array<{ - item: string; - kind: string; - progress: number; - cost: number; - tile?: WireHex; - }>; - buildings: string[]; - owned_tiles: WireHex[]; - yields: Record; - hp: number; - max_hp: number; - focus: string; - buildable?: Array<{ - item: string; - kind: string; - cost: number; - rush_gold: number; - enabled: boolean; - }>; -} - -export interface PlayerView { - turn: number; - player: PlayerId; - current_player: PlayerId; - phase: string; - is_human_turn: boolean; - resources: { - gold: number; - gold_per_turn: number; - science_per_turn: number; - culture_per_turn: number; - happiness_pool: number; - stockpile: Record; - }; - research: { - current_tech: string | null; - tech_progress: number; - tech_cost: number; - researched: string[]; - available: string[]; - }; - culture: { - current_tradition: string | null; - tradition_progress: number; - tradition_cost: number; - researched: string[]; - }; - cities: CityView[]; - units: UnitView[]; - legal_actions: LegalActionEntry[]; - score: { - gold_total: number; - city_count: number; - unit_count: number; - score_estimate: number; - }; -} - -export interface OkResponse { - id: number | null; - ok: true; - events?: Array>; - view: PlayerView; -} - -export interface ErrResponse { - id: number | null; - ok: false; - error: { code: string; message: string }; -} - -export type Response = OkResponse | ErrResponse; - -export interface Notification { - type: string; - [k: string]: unknown; -} diff --git a/tooling/claude-player/tsconfig.json b/tooling/claude-player/tsconfig.json deleted file mode 100644 index f4bd0801..00000000 --- a/tooling/claude-player/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "lib": ["ES2022"], - "strict": true, - "noImplicitAny": true, - "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "dist", - "rootDir": "src", - "resolveJsonModule": true - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist", ".local"] -}