import pkg from '@/package.json'; // OpenAPI 3.1 document for the app's public JSON API, kept in code so it can be // served machine-readable at /api/openapi (see AGENTS.md "## API"). Must be updated // whenever an endpoint changes. export const openApiSpec = { openapi: '3.1.0', info: { title: 'SpaceWanderer API', description: 'JSON API for SpaceWanderer — a single-player turn-based space adventure (one save, no accounts). World endpoints: read the generated world state and jump between adjacent solar systems.', // Sourced from package.json so it stays in sync with every version bump. version: pkg.version, }, // Empty url = current origin (the spec is served from the same host that runs the API). servers: [{ url: '' }], paths: { '/api/version': { get: { summary: 'App version', description: 'The running app version, read from package.json.', operationId: 'getVersion', responses: { 200: { description: 'Current app version', content: { 'application/json': { schema: { $ref: '#/components/schemas/AppVersion' }, }, }, }, }, }, }, '/api/world': { get: { summary: 'World state', description: 'The full world of the single active save — every known solar system, jump routes and the turn counter. Auto-initializes a new game (seeded world generation) when no save exists yet.', operationId: 'getWorld', responses: { 200: { description: 'Current world state', content: { 'application/json': { schema: { $ref: '#/components/schemas/WorldState' }, }, }, }, }, }, }, '/api/jump': { post: { summary: 'Jump to an adjacent solar system', description: 'Validates that the target is a jump route away from the current system, sets it as current (discovering it if new and generating its neighbours), increments the turn counter and writes a jump event. Returns the fresh world state.', operationId: 'jumpToSystem', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/JumpRequest' }, }, }, }, responses: { 200: { description: 'Fresh world state after the jump', content: { 'application/json': { schema: { $ref: '#/components/schemas/WorldState' }, }, }, }, 400: { description: 'Malformed body or non-integer systemId', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' }, }, }, }, 404: { description: 'Unknown system id', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' }, }, }, }, 409: { description: 'Target is not adjacent to the current system (or is the current system itself)', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' }, }, }, }, }, }, }, '/api/system': { get: { summary: 'Current system state', description: 'The bodies of the current solar system — planets and their moons (nested via parent) with hazard/explored flags — plus the save inventory and turn counter. Auto-initializes a new game when no save exists yet.', operationId: 'getSystem', responses: { 200: { description: 'Current system state', content: { 'application/json': { schema: { $ref: '#/components/schemas/SystemState' }, }, }, }, }, }, }, '/api/explore': { post: { summary: 'Explore a planet or moon', description: 'Scans one body of the save with a deterministic haul (a resource and/or a part, or nothing), marks it explored, increments the turn counter, accumulates finds into inventory and writes an explore event. Returns the fresh system state plus this scan outcome.', operationId: 'explorePlanet', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/ExploreRequest' }, }, }, }, responses: { 200: { description: 'Fresh system state plus the scan outcome', content: { 'application/json': { schema: { $ref: '#/components/schemas/ExploreResult' }, }, }, }, 400: { description: 'Malformed body or non-integer planetId', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' }, }, }, }, 404: { description: 'Unknown planet id', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' }, }, }, }, 409: { description: 'Body was already explored (each can only be scanned once)', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' }, }, }, }, }, }, }, '/api/crew': { get: { summary: 'Crew roster', description: 'The ship crew in join order — the starting crew (seeded on new game) plus members found while exploring planets/moons. Each member has one good and one bad trait and an origin. Auto-initializes a new game when no save exists yet.', operationId: 'getCrew', responses: { 200: { description: 'Crew roster', content: { 'application/json': { schema: { type: 'array', items: { $ref: '#/components/schemas/CrewMember' } }, }, }, }, }, }, }, }, components: { schemas: { AppVersion: { type: 'object', properties: { version: { type: 'string', description: 'Semver of the running build' }, }, required: ['version'], }, WorldSystem: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' }, x: { type: 'number', description: '0–100 chart coordinate' }, y: { type: 'number', description: '0–100 chart coordinate' }, discovered: { type: 'boolean' }, current: { type: 'boolean', description: 'True for the system the ship is in now' }, }, required: ['id', 'name', 'x', 'y', 'discovered', 'current'], }, WorldState: { type: 'object', properties: { systems: { type: 'array', items: { $ref: '#/components/schemas/WorldSystem' } }, edges: { type: 'array', description: 'Undirected jump routes as [fromId, toId] pairs (sorted, deduped)', items: { type: 'array', items: { type: 'integer' }, minItems: 2, maxItems: 2, }, }, turn: { type: 'integer', description: 'Turn counter — one per jump' }, }, required: ['systems', 'edges', 'turn'], }, JumpRequest: { type: 'object', properties: { systemId: { type: 'integer', description: 'solar_systems.id of the jump target' }, }, required: ['systemId'], }, Planet: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' }, type: { type: 'string', description: 'Body class, e.g. TERRESTRIAL or GAS GIANT' }, hazard: { type: 'boolean' }, explored: { type: 'boolean', description: 'True once scanned — each body can only be explored once' }, parent: { type: ['integer', 'null'], nullable: true, description: 'NULL for a planet, the parent planet id for a moon' }, }, required: ['id', 'name', 'type', 'hazard', 'explored', 'parent'], }, InventoryItem: { type: 'object', properties: { kind: { type: 'string', enum: ['resource', 'part'] }, name: { type: 'string' }, qty: { type: 'integer' }, }, required: ['kind', 'name', 'qty'], }, FoundItem: { type: 'object', properties: { kind: { type: 'string', enum: ['resource', 'part'] }, name: { type: 'string' }, qty: { type: 'integer' }, }, required: ['kind', 'name', 'qty'], }, CrewMember: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' }, role: { type: 'string', description: 'Ship role, e.g. PILOT or ENGINEER' }, trait_good: { type: 'string', description: 'The member good trait' }, trait_bad: { type: 'string', description: 'The member bad trait' }, origin: { type: 'string', description: "'STARTING CREW' or ', ' for members found while exploring" }, }, required: ['id', 'name', 'role', 'trait_good', 'trait_bad', 'origin'], }, SystemState: { type: 'object', properties: { systemId: { type: 'integer' }, systemName: { type: 'string' }, planets: { type: 'array', description: 'Planets and moons in display order — a moon always follows its parent row', items: { $ref: '#/components/schemas/Planet' }, }, inventory: { type: 'array', items: { $ref: '#/components/schemas/InventoryItem' } }, turn: { type: 'integer', description: 'Turn counter — one per jump or exploration' }, }, required: ['systemId', 'systemName', 'planets', 'inventory', 'turn'], }, ExploreRequest: { type: 'object', properties: { planetId: { type: 'integer', description: 'planets.id of the body to scan (planet or moon)' }, }, required: ['planetId'], }, ExploreResult: { allOf: [ { $ref: '#/components/schemas/SystemState' }, { type: 'object', properties: { outcome: { type: 'array', description: 'What this scan found — may be empty (some scans find nothing)', items: { $ref: '#/components/schemas/FoundItem' }, }, crewFound: { description: 'A crew member who joined the ship on this scan (~18% of scans), null when none', anyOf: [{ $ref: '#/components/schemas/CrewMember' }, { type: 'null' }], }, }, required: ['outcome', 'crewFound'], }, ], }, Error: { type: 'object', properties: { error: { type: 'string' }, }, required: ['error'], }, }, }, };