openapi: 3.0.3
info:
  title: Von Neumann Game API
  version: '25'
servers:
  - url: /
    description: Current host
security:
  - bearerAuth: []
paths:
  /api/version:
    get:
      security: []
      summary: Get API version
      responses:
        '200':
          description: Current API version
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiVersionResponse'
              example:
                apiVersion: 25
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
  /api/session:
    post:
      security: []
      summary: Create a password session
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionRequest'
            example:
              username: remi
              password: secret
      responses:
        '200':
          description: Session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponse'
              example:
                token: eyJlocalExampleToken
                expiresAt: '2026-05-28T12:00:00+00:00'
                player:
                  id: 1
                  username: remi
                  displayName: Remi
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
  /api/me:
    get:
      summary: Get authenticated player
      responses:
        '200':
          description: Current player
          content:
            application/json:
              schema:
                type: object
                required: [player]
                properties:
                  player:
                    $ref: '#/components/schemas/Player'
              example:
                player:
                  id: 1
                  username: remi
                  displayName: Remi
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/me/api-key:
    post:
      summary: Create an API key for the authenticated player
      description: >
        Returns a new API key that can be used as a Bearer token on protected
        API endpoints. The clear token is shown only in this response; only its
        hash is stored server-side.
      responses:
        '201':
          description: API key created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyResponse'
              example:
                apiKey:
                  id: 1
                  token: vng_exampleGeneratedSecret
                  label: default
                  lastFour: cret
                  createdAt: '2026-06-01T12:00:00+00:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/crafting-recipes:
    get:
      summary: List available crafting recipes
      responses:
        '200':
          description: Available crafting recipes
          content:
            application/json:
              schema:
                type: object
                required: [recipes]
                properties:
                  recipes:
                    type: array
                    items:
                      $ref: '#/components/schemas/CraftingRecipe'
              example:
                recipes:
                  - id: waypoint_bookmark
                    name: Waypoint bookmark
                    craftableBy: [manny]
                    ingredients:
                      - type: metals
                        quantity: 0.01
                        unit: earth_container_equivalent
                    durationSeconds: 600
                    output:
                      type: waypoint_bookmark
                      name: Waypoint bookmark
                      containerSpace: 0.01
                      containerSpaceUnit: earth_container_equivalent
                  - id: integrated_circuit
                    name: Integrated circuit
                    craftableBy: [atomic_3d_printer]
                    ingredients:
                      - type: micro_conductor
                        quantity: 2
                        unit: item
                      - type: ceramic_insulator
                        quantity: 2
                        unit: item
                      - type: crystal_substrate
                        quantity: 1
                        unit: item
                      - type: dopant_matrix
                        quantity: 1
                        unit: item
                      - type: deuterium
                        quantity: 0.05
                        unit: earth_container_equivalent
                    durationSeconds: 1200
                    output:
                      type: integrated_circuit
                      name: Integrated circuit
                      containerSpace: 0.001
                      containerSpaceUnit: earth_container_equivalent
                  - id: manny
                    name: Manny
                    craftableBy: [manny]
                    ingredients:
                      - type: linear_actuator
                        quantity: 6
                        unit: item
                      - type: electric_motor
                        quantity: 12
                        unit: item
                      - type: battery_pack
                        quantity: 4
                        unit: item
                      - type: integrated_circuit
                        quantity: 6
                        unit: item
                    durationSeconds: 3600
                    output:
                      type: manny
                      name: Manny
                      containerSpace: 0.05
                      containerSpaceUnit: earth_container_equivalent
                      cargoCapacity: 0.05
                      cargoCapacityUnit: earth_container_equivalent
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/probe:
    get:
      summary: Get current Neumann probe
      responses:
        '200':
          description: Current probe
          content:
            application/json:
              schema:
                type: object
                required: [probe]
                properties:
                  probe:
                    $ref: '#/components/schemas/Probe'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/probe/storage-containers:
    get:
      summary: List probe storage containers
      description: >
        Returns the probe core storage and each additional container created
        from an additional_container inventory item. The deuterium tank is not
        managed here.
      responses:
        '200':
          description: Storage containers
          content:
            application/json:
              schema:
                type: object
                required: [containers]
                properties:
                  containers:
                    type: array
                    items:
                      $ref: '#/components/schemas/StorageContainer'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/probe/storage-containers/{containerId}:
    get:
      summary: Get one storage container content
      parameters:
        - $ref: '#/components/parameters/StorageContainerId'
      responses:
        '200':
          description: Container inventory
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StorageContainerInventoryResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/probe/storage-containers/{containerId}/rules:
    patch:
      summary: Update storage routing rules for a container
      description: >
        Priority routes matching new items to this container first unless it is
        full. Exclusion avoids this container unless no other non-strict
        container can accept the item. Strict exclusion prevents automatic
        placement into this container.
      parameters:
        - $ref: '#/components/parameters/StorageContainerId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StorageContainerRules'
            example:
              priority: [metals]
              exclusion: [ice]
              strictExclusion: [manny]
      responses:
        '200':
          description: Rules updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StorageContainerRulesResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/probe/storage-moves:
    post:
      summary: Assign a Manny to move stock between containers
      description: >
        Starts a moving_stockage task on an idle onboard Manny. Unit items take
        10 seconds; resources take 10 seconds per 0.05 ECE.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StorageMoveRequest'
            examples:
              resource:
                value:
                  actorMannyId: mny_actor
                  kind: resource
                  resourceType: metals
                  amount: 0.1
                  fromContainerId: probe-core
                  toContainerId: container-itm_extra
              item:
                value:
                  actorMannyId: mny_actor
                  kind: item
                  itemIds: [itm_steel_bar, itm_steel_bar_2]
                  quantity: 2
                  toContainerId: container-itm_extra
      responses:
        '202':
          description: Storage move task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny, inventory]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
                  inventory:
                    $ref: '#/components/schemas/ProbeInventory'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Manny unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Move is impossible because of capacity or item rules
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/messages:
    get:
      summary: List messages received by the current probe
      description: >
        Returns messages where the authenticated player's probe is the
        recipient. Messages are sorted newest first, include their read
        status, and are limited to the 50 latest messages by default.
      parameters:
        - name: limit
          in: query
          required: false
          description: Maximum number of messages to return. Defaults to 50.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of newest messages to skip before returning the page.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Received probe messages
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProbeMessagesResponse'
              example:
                messages:
                  - id: 12
                    sender:
                      probeId: 2
                      name: Probe of nova
                    recipient:
                      probeId: 1
                      name: Probe of remi
                    sector:
                      relative: { x: 0, y: 0, z: 0 }
                    body: Signal local reçu.
                    status: unread
                    readAt: null
                    createdAt: '2026-06-06T12:00:00+00:00'
                    updatedAt: '2026-06-06T12:00:00+00:00'
                pagination:
                  limit: 50
                  offset: 0
                  count: 1
                  total: 67
                  hasMore: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
    post:
      summary: Send a message to another probe in the same sector
      description: >
        Creates an unread message for another probe. The recipient probe must
        currently be in the exact same sector as the sender probe.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProbeMessageSendRequest'
            example:
              recipientProbeId: 2
              body: Signal local reçu ?
      responses:
        '201':
          description: Probe message sent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProbeMessageResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Recipient probe is invalid or not in the same sector
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/messages/sent:
    get:
      summary: List messages sent by the current probe
      description: >
        Returns messages where the authenticated player's probe is the sender.
        Messages are sorted newest first and are limited to the 50 latest
        messages by default. This endpoint does not expose recipient read
        state fields.
      parameters:
        - name: limit
          in: query
          required: false
          description: Maximum number of messages to return. Defaults to 50.
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of newest messages to skip before returning the page.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Sent probe messages
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProbeSentMessagesResponse'
              example:
                messages:
                  - id: 12
                    sender:
                      probeId: 1
                      name: Probe of remi
                    recipient:
                      probeId: 2
                      name: Probe of nova
                    sector:
                      relative: { x: 0, y: 0, z: 0 }
                    body: Signal local reçu ?
                    createdAt: '2026-06-06T12:00:00+00:00'
                pagination:
                  limit: 50
                  offset: 0
                  count: 1
                  total: 3
                  hasMore: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
  /api/probe/messages/{messageId}/read:
    patch:
      summary: Mark a received probe message as read
      description: >
        Only the recipient probe can mark a message as read. Repeating the call
        on an already-read message is safe and returns the message unchanged.
      parameters:
        - $ref: '#/components/parameters/ProbeMessageId'
      responses:
        '200':
          description: Probe message marked read
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProbeMessageResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/probe/damage-warnings:
    get:
      summary: List probe movement damage warnings
      description: >
        Returns persistent warnings produced when a fragile chain of additional
        storage containers may lose one container during a sector movement.
        This rule starts at 5 additional containers with a 10% risk, then adds
        10 percentage points per extra container up to 100%.
      responses:
        '200':
          description: Current and past damage warnings for the authenticated probe
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProbeDamageWarningsResponse'
              example:
                damageWarnings:
                  - id: 7
                    type: storage_container_break
                    status: unread
                    message: From 5 additional containers onward, movement stress can break one container link. Risk is 30% for this jump.
                    phase: acceleration_end
                    scheduledAt: '2026-06-12T12:03:00+00:00'
                    sector:
                      relative: { x: 0, y: 0, z: 0 }
                    container:
                      id: cnt_extra_3
                      label: Additional container 3
                      objectId: detached-container-7
                    risk:
                      percent: 30
                      additionalContainerCount: 7
                    createdAt: '2026-06-12T12:00:00+00:00'
                    updatedAt: '2026-06-12T12:00:00+00:00'
                    readAt: null
                    resolvedAt: null
                rule:
                  type: storage_container_break
                  startsAtAdditionalContainers: 5
                  riskPerAdditionalContainerAfterFourPercent: 10
                  maximumRiskPercent: 100
                  message: From 5 additional containers onward, movement stress can break one container link. Risk is 10% at 5 containers, 20% at 6, and continues up to 100%.
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/probe/damage-warnings/{damageWarningId}:
    patch:
      summary: Mark a movement damage warning as read
      parameters:
        - $ref: '#/components/parameters/DamageWarningId'
      responses:
        '200':
          description: Damage warning marked read
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProbeDamageWarningResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/probe/visited-sectors:
    get:
      summary: List sectors already visited by the current probe
      description: >
        Returns the authenticated player's visited-sector history for the
        current probe. Coordinates are relative to the player's home sector and
        sorted by most recent visit first.
      responses:
        '200':
          description: Visited sectors for the current probe
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProbeVisitedSectorsResponse'
              example:
                visitedSectors:
                  - relativeCoordinates: { x: 0, y: 0, z: 0 }
                    firstVisitedAt: '2026-06-01T12:00:00+00:00'
                    lastVisitedAt: '2026-06-01T12:00:00+00:00'
                    visitCount: 1
                  - relativeCoordinates: { x: 1, y: 1, z: 0 }
                    firstVisitedAt: '2026-06-01T13:15:00+00:00'
                    lastVisitedAt: '2026-06-01T15:45:00+00:00'
                    visitCount: 2
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/probe/sector:
    get:
      summary: Get observable probe sector and onboard inventory
      responses:
        '200':
          description: Current sector summary and probe inventory
          content:
            application/json:
              schema:
                type: object
                required: [sector, inventory]
                properties:
                  sector:
                    $ref: '#/components/schemas/SectorObservation'
                  inventory:
                    $ref: '#/components/schemas/ProbeInventory'
              example:
                sector:
                  relativeCoordinates: { x: 0, y: 0, z: 0 }
                  distance: 0
                  knowledgeLevel: detailed
                  confidence: 1
                  objects:
                    - type: solar_system
                      name: System abc123
                      summary: Stellar system with 1 star(s) and 4 orbital body(ies).
                      radius: 5.4
                      radiusUnit: astronomical_unit
                      minableTargets:
                        - id: asteroid-abc123-0
                          type: asteroid
                          name: null
                          mass: 0.000001
                          massUnit: earth_mass
                          resources: [iron, nickel]
                          resourceTypes: [metals]
                          resourceComposition:
                            deuterium: 0
                            metals: 1
                            ice: 0
                            carbon_compounds: 0
                          resourceAmounts:
                            deuterium: 0
                            metals: 1
                            ice: 0
                            carbon_compounds: 0
                    - id: mine-rock
                      type: asteroid
                      name: null
                      estimated: false
                      summary: Wandering asteroid body.
                      mass: 0.000001
                      massUnit: earth_mass
                      radius: 0.001
                      radiusUnit: earth_radius
                      dangerLevel: low
                      resources: [iron, nickel]
                      resourceTypes: [metals]
                      resourceComposition:
                        deuterium: 0
                        metals: 1
                        ice: 0
                        carbon_compounds: 0
                      resourceAmounts:
                        deuterium: 0
                        metals: 1
                        ice: 0
                        carbon_compounds: 0
                      mannyMineable: true
                      composition: iron
                  probes:
                    - id: 2
                      name: Probe of nova
                      moving: false
                  scan:
                    currentSectorResidenceSeconds: 42
                    requiredResidenceSeconds: 0
                    scanQuality: 1
                inventory:
                  capacity: 1
                  capacityUnit: earth_container_equivalent
                  usedCapacity: 0.5
                  freeCapacity: 0.5
                  items:
                    - id: probe-1-atomic-3d-printer
                      type: atomic_3d_printer
                      name: Imprimante atomique
                      containerSpace: 0.3
                      currentTask: null
                      taskProgressPercent: 0
                      container:
                        id: probe-core
                        kind: probe
                        label: Sonde
                        sortOrder: 0
                    - id: mny_8d4a2c1f5a91b334
                      type: manny
                      name: manny-1
                      containerSpace: 0.05
                      currentTask: null
                      taskProgressPercent: 0
                      location:
                        type: probe
                      cargo:
                        capacity: 0.05
                        deuterium: 0
                        metals: 0
                        ice: 0
                        organicCompounds: 0
                        capacityUnit: earth_container_equivalent
                      container:
                        id: probe-core
                        kind: probe
                        label: Sonde
                        sortOrder: 0
                  resourceStocks:
                    - id: probe-1-stock-metals
                      type: metals
                      name: Métaux
                      amount: 0
                      containerSpace: 0
                      capacityUnit: earth_container_equivalent
                      containers: []
                    - id: probe-1-stock-ice
                      type: ice
                      name: Glace
                      amount: 0
                      containerSpace: 0
                      capacityUnit: earth_container_equivalent
                      containers: []
                    - id: probe-1-stock-organic-compounds
                      type: carbon_compounds
                      name: Composés organiques
                      amount: 0
                      containerSpace: 0
                      capacityUnit: earth_container_equivalent
                      containers: []
                  containers:
                    - id: probe-core
                      kind: probe
                      label: Sonde
                      sortOrder: 0
                      capacity: 1
                      usedCapacity: 0.5
                      freeCapacity: 0.5
                      capacityUnit: earth_container_equivalent
                      rules:
                        priority: []
                        exclusion: []
                        strictExclusion: []
                  externalTanks:
                    - id: probe-1-deuterium-tank
                      type: deuterium
                      name: Cuve externe de deutérium
                      fillPercent: 100
                      external: true
                      usesCargoCapacity: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Probe is dead
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '400':
          description: Sensors unavailable while cruising
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: sensors_unavailable
                  message: External sensors are unavailable at current relativistic velocity.
  /api/probe/move:
    post:
      summary: Start an asynchronous intersector movement
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProbeMoveRequest'
            example:
              target: { x: 1, y: 1, z: 0 }
      responses:
        '202':
          description: Movement accepted
          content:
            application/json:
              schema:
                type: object
                required: [movement]
                properties:
                  movement:
                    $ref: '#/components/schemas/ProbeMovement'
              example:
                movement:
                  status: preparing
                  origin: { x: 0, y: 0, z: 0 }
                  target: { x: 1, y: 1, z: 0 }
                  distance: 1
                  fuelCostDeuterium: 2
                  startedAt: '2026-05-28T12:00:00+00:00'
                  arrivalAt: '2026-05-28T12:40:00+00:00'
                  phase: preparing
                  secondsRemaining: 2400
                  sensorMode: normal
                  estimatedVelocityC: 0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Probe is already moving or dead
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                moving:
                  value:
                    error:
                      code: probe_already_moving
                      message: The probe is already moving between sectors.
                dead:
                  value:
                    error:
                      code: probe_dead
                      message: The probe is no longer operational.
        '422':
          description: Insufficient deuterium
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: insufficient_fuel
                  message: Insufficient deuterium for movement.
  /api/probe/inventory/{itemId}:
    get:
      summary: Get task state for an onboard inventory item
      parameters:
        - name: itemId
          in: path
          required: true
          schema:
            type: string
          example: mny_8d4a2c1f5a91b334
      responses:
        '200':
          description: Inventory item task state
          content:
            application/json:
              schema:
                type: object
                required: [item]
                properties:
                  item:
                    $ref: '#/components/schemas/ProbeInventoryItemTask'
              example:
                item:
                  id: mny_8d4a2c1f5a91b334
                  type: manny
                  name: manny-1
                  currentTask: null
                  taskProgressPercent: 0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/probe/inventory/{itemId}/jettison:
    post:
      summary: Jettison an inventory entry into space
      description: >
        Discards stored metals/non-metal materials by amount, ejects an idle onboard Manny
        into the current sector, or adds supported crafted items to an aggregated
        drifting-item stack in the current sector. Non-persisted core equipment
        and additional containers cannot be jettisoned. The external deuterium
        tank is not jettisonable.
      parameters:
        - name: itemId
          in: path
          required: true
          schema:
            type: string
          examples:
            metals:
              value: probe-1-stock-metals
            manny:
              value: mny_8d4a2c1f5a91b334
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProbeInventoryJettisonRequest'
            examples:
              partialStock:
                value:
                  amount: 0.05
                  containerId: probe-core
              wholeItem:
                value: {}
      responses:
        '200':
          description: Inventory entry jettisoned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProbeInventoryJettisonResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The requested Manny cannot be ejected now
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Entry cannot be jettisoned or requested amount is unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies:
    get:
      summary: List persisted Manny robots for the current probe
      responses:
        '200':
          description: Manny robots
          content:
            application/json:
              schema:
                type: object
                required: [mannies]
                properties:
                  mannies:
                    type: array
                    items:
                      $ref: '#/components/schemas/Manny'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/probe/mannies/{mannyId}:
    patch:
      summary: Rename a Manny robot
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannyRenameRequest'
            example:
              name: atelier
      responses:
        '200':
          description: Manny renamed
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Duplicate Manny name
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/repair:
    post:
      summary: Start restoring probe integrity with a Manny
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannyRepairRequest'
            example:
              integrityPercent: 2
      responses:
        '202':
          description: Repair task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Manny unavailable or probe cannot be repaired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Insufficient metals
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/mine:
    post:
      summary: Start mining a small body with a Manny
      description: >
        Pick objectId from a detailed SectorObject.id or from a solar system
        minableTargets entry. Select resources from resourceTypes. For
        asteroids, resourceAmounts is the authoritative remaining reserve in
        equivalent earth containers; resourceComposition/resourceProfile define
        how a multi-resource targetAmount is split.
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannyMineRequest'
            example:
              objectId: mine-rock
              resources: [metals, ice, carbon_compounds]
              targetAmount: 0.05
      responses:
        '202':
          description: Mining task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Manny unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Invalid target, unavailable resource, or insufficient cargo capacity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/craft:
    post:
      summary: Start crafting a Manny-built inventory item
      description: Atomic-printer recipes must be started through `/api/probe/atomic-printer/craft`; that endpoint reserves an available Manny as printer assistant.
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannyCraftRequest'
            example:
              recipe: waypoint_bookmark
      responses:
        '202':
          description: Crafting task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Manny unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Insufficient resources, ingredients, or cargo capacity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/atomic-printer/craft:
    post:
      summary: Start crafting an atomic-printer item
      description: >
        Starts a recipe whose `craftableBy` includes `atomic_3d_printer`.
        The atomic printer reserves one available Manny aboard the probe for
        loading and unloading; that Manny exposes the
        `assisting_atomic_printer` task until the craft completes or is
        recalled.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AtomicPrinterCraftRequest'
            example:
              recipe: integrated_circuit
      responses:
        '202':
          description: Atomic-printer crafting task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny, inventory]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
                  inventory:
                    $ref: '#/components/schemas/ProbeInventory'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Atomic printer busy or no Manny available to assist it
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Insufficient resources, ingredients, or cargo capacity
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/salvage:
    post:
      summary: Recover a drifting sector object with a Manny
      description: >
        Starts a five-minute recovery task. The target is checked again when
        the delay ends; if another player has already recovered it, the task
        completes with a failed result and no object is transferred.
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannySalvageRequest'
            example:
              objectId: manny-mny_abandoned
      responses:
        '202':
          description: Recovery task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Manny unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Object is not recoverable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/detach-storage-container:
    post:
      summary: Detach an additional storage container
      description: >
        Starts a detaching_storage_container task on an idle onboard Manny.
        The target must be an additional storage container, never probe-core.
        The container, its stored resources/items, and routing rules are
        removed from the probe inventory when the order is accepted. On
        completion it becomes either a visible drifting detached container or a
        hidden container attached to an asteroid.
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannyDetachStorageContainerRequest'
            examples:
              drifting:
                value:
                  containerId: container-itm_extra
                  mode: drifting
              hidden:
                value:
                  containerId: container-itm_extra
                  mode: hidden_on_asteroid
                  objectId: asteroid-abc123
      responses:
        '202':
          description: Detach task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Manny unavailable, probe moving, or probe no longer operational
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Container or asteroid target is invalid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/inspect-asteroid:
    post:
      summary: Inspect an asteroid with a Manny
      description: >
        Sends a Manny to an asteroid and back without mining. If the asteroid
        has a hidden detached storage container, the Manny task includes
        artificialObjectDetected with the detached object id, but not the
        container contents.
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannyInspectAsteroidRequest'
            example:
              objectId: asteroid-abc123
      responses:
        '202':
          description: Inspection task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Manny unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Target is not an asteroid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/recover-storage-container:
    post:
      summary: Recover a detached storage container
      description: >
        Starts a salvage-duration recovery task for a detached storage
        container. Drifting containers can also be recovered through the
        salvage endpoint. Hidden containers are recovered by the object id
        returned from mining or inspect-asteroid detection.
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannyRecoverStorageContainerRequest'
            example:
              objectId: detached-container-container-itm_extra
              source: asteroid
      responses:
        '202':
          description: Recovery task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Detached container not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Manny unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Detached container cannot be recovered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/install-bookmark:
    post:
      summary: Start installing a waypoint bookmark with a Manny
      description: >
        Assigns an idle onboard Manny to install one stocked waypoint bookmark
        on a current-sector celestial object. The Manny consumes one bookmark
        item when the order is accepted, spends 10 seconds giving it the needed
        impulse, then the normal waypoint bookmark persistence is applied to the
        target object.
      parameters:
        - $ref: '#/components/parameters/MannyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MannyWaypointBookmarkInstallRequest'
            example:
              objectId: asteroid-abc123
              name: North ice marker
      responses:
        '202':
          description: Bookmark installation task accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Manny, bookmark item, or target object not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Manny unavailable, probe already moving, or probe no longer operational
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Target is not a celestial object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/probe/mannies/{mannyId}/recall:
    post:
      summary: Recall an exterior Manny to the probe
      parameters:
        - $ref: '#/components/parameters/MannyId'
      responses:
        '202':
          description: Recall accepted
          content:
            application/json:
              schema:
                type: object
                required: [manny]
                properties:
                  manny:
                    $ref: '#/components/schemas/Manny'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Manny is outside the probe current sector
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/sector:
    get:
      summary: Observe a sector by player-relative coordinates
      description: >
        Coordinates are relative to the authenticated player's reference frame.
        Absolute sector coordinates are never exposed. Detail depends on whether
        the sector is current, visited, or only passively scanned from the probe
        current sector. During blind cruise, only already visited sectors can be
        consulted as historical cartography.
      parameters:
        - name: x
          in: query
          required: true
          schema:
            type: integer
          example: 1
        - name: y
          in: query
          required: true
          schema:
            type: integer
          example: 1
        - name: z
          in: query
          required: true
          schema:
            type: integer
          example: 0
      responses:
        '200':
          description: Sector observation
          content:
            application/json:
              schema:
                type: object
                required: [sector]
                properties:
                  sector:
                    $ref: '#/components/schemas/SectorObservation'
              examples:
                detailed:
                  value:
                    sector:
                      relativeCoordinates: { x: 0, y: 0, z: 0 }
                      distance: 0
                      knowledgeLevel: detailed
                      confidence: 1
                      objects:
                        - type: solar_system
                          name: System abc123
                          estimated: false
                          summary: Stellar system with 1 star(s) and 4 orbital body(ies).
                          radius: 5.4
                          radiusUnit: astronomical_unit
                          dangerLevel: low
                        - id: mine-rock
                          type: asteroid
                          name: null
                          estimated: false
                          summary: Wandering asteroid body.
                          mass: 0.000001
                          massUnit: earth_mass
                          radius: 0.001
                          radiusUnit: earth_radius
                          dangerLevel: low
                          resources: [iron, nickel]
                          resourceTypes: [metals]
                          resourceComposition:
                            deuterium: 0
                            metals: 1
                            ice: 0
                            carbon_compounds: 0
                          resourceAmounts:
                            deuterium: 0
                            metals: 1
                            ice: 0
                            carbon_compounds: 0
                          mannyMineable: true
                          composition: iron
                      scan:
                        currentSectorResidenceSeconds: 42
                        requiredResidenceSeconds: 0
                        scanQuality: 1
                neighborScan:
                  value:
                    sector:
                      relativeCoordinates: { x: 1, y: 1, z: 0 }
                      distance: 1
                      knowledgeLevel: neighbor_scan
                      confidence: 0.82
                      estimatedObjects:
                        star: true
                        planetCountMin: 3
                        planetCountMax: 7
                        blackHoleProbability: 0.01
                        dangerEstimate: low
                        signalAge: recent
                      scan:
                        currentSectorResidenceSeconds: 1832
                        requiredResidenceSeconds: 300
                        scanQuality: 1
                distantScan:
                  value:
                    sector:
                      relativeCoordinates: { x: 2, y: 0, z: 0 }
                      distance: 2
                      knowledgeLevel: distant_scan
                      confidence: 0.41
                      possibleObjects: [stellar_mass_detected, dust_cloud_possible]
                      dangerEstimate: moderate
                      scan:
                        currentSectorResidenceSeconds: 3600
                        requiredResidenceSeconds: 1800
                        scanQuality: 0.5
                longRange:
                  value:
                    sector:
                      relativeCoordinates: { x: 3, y: 3, z: 0 }
                      distance: 3
                      knowledgeLevel: long_range_estimation
                      confidence: 0.12
                      navigationalRisk: high
                      message: Insufficient sensor accuracy.
                      scan:
                        currentSectorResidenceSeconds: 28800
                        requiredResidenceSeconds: 7200
                        scanQuality: 1
        '400':
          description: Bad request or insufficient scan data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidCoordinates:
                  value:
                    error:
                      code: bad_request
                      message: 'Invalid coordinates (1, 0, 0): sum must be even (got 1)'
                insufficientScanData:
                  value:
                    error:
                      code: insufficient_scan_data
                      message: Too little passive scan data available to estimate nearby objects.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Probe is dead
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Use a session token from /api/session or an API key generated from /api/me/api-key.
  parameters:
    MannyId:
      name: mannyId
      in: path
      required: true
      schema:
        type: string
      example: mny_8d4a2c1f5a91b334
    StorageContainerId:
      name: containerId
      in: path
      required: true
      schema:
        type: string
      example: probe-core
    ProbeMessageId:
      name: messageId
      in: path
      required: true
      schema:
        type: integer
        minimum: 1
      example: 12
    DamageWarningId:
      name: damageWarningId
      in: path
      required: true
      schema:
        type: integer
        minimum: 1
      example: 7
  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: unauthorized
              message: Missing or invalid bearer token
    MethodNotAllowed:
      description: Method not allowed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum: [bad_request, unauthorized, not_found, method_not_allowed, insufficient_scan_data, sensors_unavailable, probe_already_moving, probe_dead, probe_trapped_by_black_hole, insufficient_fuel, same_destination, manny_not_found, duplicate_manny_name, manny_busy, manny_out_of_range, manny_not_on_probe, probe_integrity_full, insufficient_metals, insufficient_ice, insufficient_carbon_compounds, insufficient_deuterium, insufficient_crafting_ingredients, invalid_recipe, atomic_printer_busy, no_available_manny, invalid_mining_target, invalid_salvage_target, invalid_storage_container, storage_container_not_detachable, invalid_asteroid_target, detached_container_not_found, detached_container_not_recoverable, resource_unavailable, insufficient_target_resources, insufficient_cargo_capacity, item_not_jettisonable, item_not_movable, invalid_storage_move, storage_container_not_found, nothing_to_jettison, insufficient_inventory_amount, waypoint_bookmark_not_found, bookmark_target_not_found, invalid_bookmark_target, invalid_message_recipient, probe_not_in_same_sector, internal_error]
            message:
              type: string
    ApiVersionResponse:
      type: object
      required: [apiVersion]
      properties:
        apiVersion:
          type: integer
          minimum: 1
          description: Monotonic public API version. Increment it when the API contract changes.
    SessionRequest:
      type: object
      required: [username, password]
      properties:
        username:
          type: string
        password:
          type: string
          format: password
    SessionResponse:
      type: object
      required: [token, expiresAt, player]
      properties:
        token:
          type: string
        expiresAt:
          type: string
          format: date-time
        player:
          $ref: '#/components/schemas/Player'
    Player:
      type: object
      required: [id, username]
      properties:
        id:
          type: integer
        username:
          type: string
        displayName:
          type: string
          nullable: true
    ApiKeyResponse:
      type: object
      required: [apiKey]
      properties:
        apiKey:
          $ref: '#/components/schemas/ApiKey'
    ApiKey:
      type: object
      required: [id, token, label, lastFour, createdAt]
      properties:
        id:
          type: integer
        token:
          type: string
          description: Clear API key shown only once. Use it as the Bearer token.
        label:
          type: string
        lastFour:
          type: string
        createdAt:
          type: string
          format: date-time
    CraftingRecipe:
      type: object
      required: [id, name, craftableBy, ingredients, durationSeconds, output]
      properties:
        id:
          type: string
          enum: [waypoint_bookmark, steel_bar, steel_plate, additional_container, micro_conductor, ceramic_insulator, crystal_substrate, dopant_matrix, integrated_circuit, electric_motor, battery_pack, linear_actuator, manny]
        name:
          type: string
        craftableBy:
          type: array
          description: Inventory item types that can start this recipe.
          items:
            type: string
            enum: [manny, atomic_3d_printer]
        ingredients:
          type: array
          items:
            $ref: '#/components/schemas/CraftingRecipeIngredient'
        durationSeconds:
          type: integer
          minimum: 0
          description: Real seconds needed to complete the crafting task.
        output:
          $ref: '#/components/schemas/CraftingRecipeOutput'
    CraftingRecipeIngredient:
      type: object
      required: [type, quantity, unit]
      properties:
        type:
          type: string
          enum: [deuterium, metals, ice, carbon_compounds, steel_bar, steel_plate, micro_conductor, ceramic_insulator, crystal_substrate, dopant_matrix, integrated_circuit, electric_motor, battery_pack, linear_actuator]
        quantity:
          type: number
          minimum: 0
          exclusiveMinimum: true
        unit:
          type: string
          enum: [earth_container_equivalent, item]
        kind:
          type: string
          enum: [resource, item]
    CraftingRecipeOutput:
      type: object
      required: [type, name, containerSpace, containerSpaceUnit]
      properties:
        type:
          type: string
          enum: [waypoint_bookmark, steel_bar, steel_plate, additional_container, micro_conductor, ceramic_insulator, crystal_substrate, dopant_matrix, integrated_circuit, electric_motor, battery_pack, linear_actuator, manny]
        name:
          type: string
        containerSpace:
          type: number
          minimum: 0
        containerSpaceUnit:
          type: string
          enum: [earth_container_equivalent]
        cargoCapacity:
          type: number
          minimum: 0
          description: Present only when the recipe output is a Manny.
        cargoCapacityUnit:
          type: string
          enum: [earth_container_equivalent]
          description: Present only when the recipe output is a Manny.
        capacityBonus:
          type: number
          minimum: 0
          description: Present on additional_container; each one creates one storage container.
        capacityBonusUnit:
          type: string
          enum: [earth_container_equivalent]
    Vector:
      type: object
      required: [x, y, z]
      properties:
        x: { type: number }
        y: { type: number }
        z: { type: number }
    MineableResourceType:
      type: string
      enum: [deuterium, metals, ice, carbon_compounds]
    ResourceHintList:
      type: array
      description: >
        Raw sensor/material hints emitted by the sector generator. Asteroids
        use strings such as iron, nickel, water_ice or carbon; clients should
        use resourceTypes/resourceComposition for mining decisions.
      items:
        type: string
    ResourceComposition:
      type: object
      required: [deuterium, metals, ice, carbon_compounds]
      additionalProperties: false
      description: >
        Normalized mineable-resource shares. Values are rounded to four
        decimals and normally sum to 1 when at least one resource is present.
      properties:
        deuterium:
          type: number
          minimum: 0
          maximum: 1
        metals:
          type: number
          minimum: 0
          maximum: 1
        ice:
          type: number
          minimum: 0
          maximum: 1
        carbon_compounds:
          type: number
          minimum: 0
          maximum: 1
    ResourceAmountMap:
      type: object
      required: [deuterium, metals, ice, carbon_compounds]
      additionalProperties: false
      description: >
        Remaining material reserves in equivalent earth containers. Asteroids
        expose this exact four-key object; mining subtracts from these values.
      properties:
        deuterium:
          type: number
          minimum: 0
        metals:
          type: number
          minimum: 0
        ice:
          type: number
          minimum: 0
        carbon_compounds:
          type: number
          minimum: 0
    MiningTarget:
      type: object
      required: [id, type, name, mass, resources, resourceTypes, resourceComposition]
      properties:
        id:
          type: string
          description: Object id to pass as objectId when starting a mining order.
        type:
          type: string
          enum: [planet, asteroid]
        name:
          type: string
          nullable: true
        mass:
          type: number
        massUnit:
          type: string
          enum: [earth_mass]
        radius:
          type: number
        radiusUnit:
          type: string
          enum: [earth_radius]
        resources:
          $ref: '#/components/schemas/ResourceHintList'
        resourceTypes:
          type: array
          description: Mineable resource categories present on this target.
          items:
            $ref: '#/components/schemas/MineableResourceType'
        resourceComposition:
          $ref: '#/components/schemas/ResourceComposition'
        resourceAmounts:
          $ref: '#/components/schemas/ResourceAmountMap'
        composition:
          type: string
          description: Asteroid composition class such as iron, silicate, carbonaceous, ice or rare_metals.
        sizeCategory:
          type: string
          enum: [small, medium, large]
        category:
          type: string
          description: Present for planet targets.
        waypointBookmarks:
          type: array
          items:
            $ref: '#/components/schemas/WaypointBookmarkHistory'
    ProbeMoveRequest:
      type: object
      required: [target]
      properties:
        target:
          $ref: '#/components/schemas/Vector'
    ProbeVisitedSectorsResponse:
      type: object
      required: [visitedSectors]
      properties:
        visitedSectors:
          type: array
          items:
            $ref: '#/components/schemas/VisitedSector'
    VisitedSector:
      type: object
      required: [relativeCoordinates, firstVisitedAt, lastVisitedAt, visitCount]
      properties:
        relativeCoordinates:
          $ref: '#/components/schemas/Vector'
        firstVisitedAt:
          type: string
          format: date-time
        lastVisitedAt:
          type: string
          format: date-time
        visitCount:
          type: integer
          minimum: 1
    ProbeMovement:
      type: object
      required: [status, origin, target, distance, fuelCostDeuterium, startedAt, arrivalAt]
      properties:
        status:
          type: string
          enum: [preparing, accelerating, cruising, decelerating, arrived, failed, destroyed]
        origin:
          $ref: '#/components/schemas/Vector'
        target:
          $ref: '#/components/schemas/Vector'
        distance:
          type: integer
        fuelCostDeuterium:
          type: number
        startedAt:
          type: string
          format: date-time
        arrivalAt:
          type: string
          format: date-time
        phase:
          type: string
          enum: [idle, preparing, accelerating, cruising, decelerating, arrived, failed, destroyed]
        secondsRemaining:
          type: integer
        sensorMode:
          type: string
          enum: [normal, degraded, blind]
        estimatedVelocityC:
          type: number
    Probe:
      type: object
      required: [id, name, status, fuel, sensorMode, movement, inventory]
      properties:
        id:
          type: integer
        name:
          type: string
        status:
          type: string
          enum: [idle, preparing, accelerating, cruising, decelerating, orbiting, disabled, dead]
        fuel:
          type: object
          properties:
            deuterium:
              type: number
        sensorMode:
          type: string
          enum: [normal, degraded, blind]
        sector:
          type: object
          nullable: true
          properties:
            relative:
              $ref: '#/components/schemas/Vector'
        movement:
          nullable: true
          $ref: '#/components/schemas/ProbeMovement'
        navigation:
          type: object
          properties:
            velocityC: { type: number }
            accelerationCPerDay: { type: number }
            direction:
              $ref: '#/components/schemas/Vector'
        systems:
          type: object
          properties:
            integrityPercent:
              type: number
              minimum: 0
              maximum: 100
            energyStored:
              type: number
            internalClockRate:
              type: number
            currentTask:
              type: string
              nullable: true
        inventory:
          $ref: '#/components/schemas/ProbeInventory'
    StorageContainer:
      type: object
      required: [id, kind, label, sortOrder, capacity, usedCapacity, freeCapacity, capacityUnit, rules]
      properties:
        id:
          type: string
          description: Stable storage container uid. probe-core is the probe hull.
          example: probe-core
        kind:
          type: string
          enum: [probe, container]
        label:
          type: string
          example: Sonde
        sortOrder:
          type: integer
        capacity:
          type: number
          example: 1
        usedCapacity:
          type: number
        freeCapacity:
          type: number
        capacityUnit:
          type: string
          enum: [earth_container_equivalent]
        rules:
          $ref: '#/components/schemas/StorageContainerRules'
    StorageContainerSummary:
      type: object
      required: [id, kind, label, sortOrder]
      properties:
        id:
          type: string
        kind:
          type: string
          enum: [probe, container]
        label:
          type: string
        sortOrder:
          type: integer
    StorageContainerRules:
      type: object
      properties:
        priority:
          type: array
          items:
            type: string
          description: Item/resource types routed here first when new stock arrives.
        exclusion:
          type: array
          items:
            type: string
          description: Item/resource types routed here only if no regular destination has room.
        strictExclusion:
          type: array
          items:
            type: string
          description: Item/resource types never automatically routed here.
    StorageContainerRulesResponse:
      type: object
      required: [container, inventory]
      properties:
        container:
          $ref: '#/components/schemas/StorageContainer'
        inventory:
          $ref: '#/components/schemas/ProbeInventory'
    StorageContainerInventoryResponse:
      type: object
      required: [container, inventory]
      properties:
        container:
          $ref: '#/components/schemas/StorageContainer'
        inventory:
          type: object
          required: [capacityUnit, items, resourceStocks]
          properties:
            capacityUnit:
              type: string
              enum: [earth_container_equivalent]
            items:
              type: array
              items:
                $ref: '#/components/schemas/ProbeInventoryItem'
            resourceStocks:
              type: array
              items:
                $ref: '#/components/schemas/ProbeResourceStock'
    StorageMoveRequest:
      type: object
      required: [actorMannyId, kind, toContainerId]
      properties:
        actorMannyId:
          type: string
          description: Idle onboard Manny that will perform the move.
        kind:
          type: string
          enum: [resource, item, manny]
        toContainerId:
          type: string
        fromContainerId:
          type: string
          description: Required for resource moves.
        resourceType:
          type: string
          enum: [metals, ice, carbon_compounds]
        amount:
          type: number
          description: Required for resource moves, in ECE.
        itemId:
          type: string
          description: Single item id for item moves. Use itemIds for batch moves.
        itemIds:
          type: array
          description: Multiple item ids for batch item moves.
          items:
            type: string
        targetMannyId:
          type: string
          description: Single Manny id for Manny moves. Use targetMannyIds for batch moves.
        targetMannyIds:
          type: array
          description: Multiple Manny ids for batch Manny storage moves.
          items:
            type: string
        quantity:
          type: integer
          minimum: 1
          description: Optional cap applied to itemIds or targetMannyIds.
    ProbeMessageSendRequest:
      type: object
      required: [recipientProbeId, body]
      properties:
        recipientProbeId:
          type: integer
          minimum: 1
          description: Probe id of another probe currently in the same sector.
        body:
          type: string
          minLength: 1
          maxLength: 2000
    ProbeMessageResponse:
      type: object
      required: [message]
      properties:
        message:
          $ref: '#/components/schemas/ProbeMessage'
    ProbeMessagesResponse:
      type: object
      required: [messages, pagination]
      properties:
        messages:
          type: array
          items:
            $ref: '#/components/schemas/ProbeMessage'
        pagination:
          $ref: '#/components/schemas/Pagination'
    ProbeSentMessagesResponse:
      type: object
      required: [messages, pagination]
      properties:
        messages:
          type: array
          items:
            $ref: '#/components/schemas/ProbeSentMessage'
        pagination:
          $ref: '#/components/schemas/Pagination'
    Pagination:
      type: object
      required: [limit, offset, count, total, hasMore]
      properties:
        limit:
          type: integer
          minimum: 1
        offset:
          type: integer
          minimum: 0
        count:
          type: integer
          minimum: 0
        total:
          type: integer
          minimum: 0
        hasMore:
          type: boolean
    ProbeMessage:
      type: object
      required: [id, sender, recipient, sector, body, status, readAt, createdAt, updatedAt]
      properties:
        id:
          type: integer
        sender:
          $ref: '#/components/schemas/ProbeMessageEndpoint'
        recipient:
          $ref: '#/components/schemas/ProbeMessageEndpoint'
        sector:
          type: object
          required: [relative]
          properties:
            relative:
              $ref: '#/components/schemas/Vector'
        body:
          type: string
        status:
          type: string
          enum: [unread, read]
        readAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ProbeSentMessage:
      type: object
      required: [id, sender, recipient, sector, body, createdAt]
      properties:
        id:
          type: integer
        sender:
          $ref: '#/components/schemas/ProbeMessageEndpoint'
        recipient:
          $ref: '#/components/schemas/ProbeMessageEndpoint'
        sector:
          type: object
          required: [relative]
          properties:
            relative:
              $ref: '#/components/schemas/Vector'
        body:
          type: string
        createdAt:
          type: string
          format: date-time
    ProbeDamageWarningsResponse:
      type: object
      required: [damageWarnings, rule]
      properties:
        damageWarnings:
          type: array
          items:
            $ref: '#/components/schemas/ProbeDamageWarning'
        rule:
          $ref: '#/components/schemas/ProbeDamageWarningRule'
    ProbeDamageWarningResponse:
      type: object
      required: [damageWarning]
      properties:
        damageWarning:
          $ref: '#/components/schemas/ProbeDamageWarning'
    ProbeDamageWarning:
      type: object
      required: [id, type, status, message, phase, scheduledAt, sector, container, risk, createdAt, updatedAt, readAt, resolvedAt]
      properties:
        id:
          type: integer
        type:
          type: string
          enum: [storage_container_break]
        status:
          type: string
          enum: [unread, read]
        message:
          type: string
        phase:
          type: string
          enum: [acceleration_end, deceleration_start]
        scheduledAt:
          type: string
          format: date-time
        sector:
          type: object
          required: [relative]
          properties:
            relative:
              $ref: '#/components/schemas/Vector'
        container:
          type: object
          required: [id, label, objectId]
          properties:
            id:
              type: string
            label:
              type: string
            objectId:
              type: string
        risk:
          type: object
          required: [percent, additionalContainerCount]
          properties:
            percent:
              type: integer
              minimum: 0
              maximum: 100
            additionalContainerCount:
              type: integer
              minimum: 0
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        readAt:
          type: string
          format: date-time
          nullable: true
        resolvedAt:
          type: string
          format: date-time
          nullable: true
    ProbeDamageWarningRule:
      type: object
      required: [type, startsAtAdditionalContainers, riskPerAdditionalContainerAfterFourPercent, maximumRiskPercent, message]
      properties:
        type:
          type: string
          enum: [storage_container_break]
        startsAtAdditionalContainers:
          type: integer
          example: 5
        riskPerAdditionalContainerAfterFourPercent:
          type: integer
          example: 10
        maximumRiskPercent:
          type: integer
          example: 100
        message:
          type: string
    ProbeMessageEndpoint:
      type: object
      required: [probeId, name]
      properties:
        probeId:
          type: integer
        name:
          type: string
    ProbeInventory:
      type: object
      required: [capacity, capacityUnit, usedCapacity, freeCapacity, items, resourceStocks, containers, externalTanks]
      properties:
        capacity:
          type: number
          description: Maximum transport capacity in equivalent earth containers.
          example: 1
        capacityUnit:
          type: string
          enum: [earth_container_equivalent]
        usedCapacity:
          type: number
          example: 0.5
        freeCapacity:
          type: number
          example: 0.5
        items:
          type: array
          items:
            $ref: '#/components/schemas/ProbeInventoryItem'
        resourceStocks:
          type: array
          items:
            $ref: '#/components/schemas/ProbeResourceStock'
        containers:
          type: array
          description: Probe core and additional storage containers, including their routing rules.
          items:
            $ref: '#/components/schemas/StorageContainer'
        externalTanks:
          type: array
          description: Special external tanks that do not consume cargo capacity.
          items:
            $ref: '#/components/schemas/ProbeExternalTank'
    ProbeResourceStock:
      type: object
      required: [id, type, name, amount, containerSpace, capacityUnit, containers]
      properties:
        id:
          type: string
          description: Stable inventory id used for jettison orders.
          example: probe-1-stock-metals
        type:
          type: string
          enum: [metals, ice, carbon_compounds]
        name:
          type: string
        amount:
          type: number
          description: Stored amount in equivalent earth containers.
        containerSpace:
          type: number
        capacityUnit:
          type: string
          enum: [earth_container_equivalent]
        containers:
          type: array
          description: Per-container placement lines for this resource stock.
          items:
            type: object
            required: [container, amount, containerSpace, capacityUnit]
            properties:
              container:
                $ref: '#/components/schemas/StorageContainerSummary'
              amount:
                type: number
              containerSpace:
                type: number
              capacityUnit:
                type: string
                enum: [earth_container_equivalent]
    ProbeExternalTank:
      type: object
      required: [id, type, name, fillPercent, external, usesCargoCapacity]
      properties:
        id:
          type: string
        type:
          type: string
          enum: [deuterium]
        name:
          type: string
        fillPercent:
          type: number
          minimum: 0
          maximum: 100
        external:
          type: boolean
          enum: [true]
        usesCargoCapacity:
          type: boolean
          enum: [false]
    ProbeInventoryJettisonRequest:
      type: object
      properties:
        amount:
          type: number
          minimum: 0
          exclusiveMinimum: true
          description: >
            Amount to discard. For stored metals/non-metal materials this uses
            equivalent earth containers; for the deuterium tank this uses tank
            percentage points. Omit it to discard the full selected stock.
        containerId:
          type: string
          description: Optional source container id for stored resource jettison.
    ProbeInventoryJettisonResponse:
      type: object
      required: [inventory]
      properties:
        inventory:
          $ref: '#/components/schemas/ProbeInventory'
        jettisoned:
          type: object
          nullable: true
          properties:
            type:
              type: string
              enum: [deuterium, metals, ice, carbon_compounds, waypoint_bookmark, steel_bar, steel_plate, micro_conductor, ceramic_insulator, crystal_substrate, dopant_matrix, integrated_circuit, electric_motor, battery_pack, linear_actuator]
            amount:
              type: number
              description: Present for discarded resources and deuterium.
            quantity:
              type: integer
              description: Present for crafted items added to a drifting sector stack.
            driftingQuantity:
              type: integer
              description: Total quantity of this crafted item type now drifting in the current sector.
            objectId:
              type: string
              description: Current-sector drifting item object id for crafted item jettison.
            containerSpace:
              type: number
        manny:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Manny'
    MannyWaypointBookmarkInstallRequest:
      type: object
      required: [objectId, name]
      properties:
        objectId:
          type: string
          description: Celestial object id in the probe current sector. Solar-system orbital bodies can be targeted by their object id.
        name:
          type: string
          minLength: 1
          maxLength: 80
          description: Player-facing name to assign to the target object.
    ProbeInventoryItem:
      type: object
      required: [id, type, name, containerSpace, currentTask, taskProgressPercent]
      properties:
        id:
          type: string
        type:
          type: string
          enum: [atomic_3d_printer, manny, waypoint_bookmark, steel_bar, steel_plate, additional_container, micro_conductor, ceramic_insulator, crystal_substrate, dopant_matrix, integrated_circuit, electric_motor, battery_pack, linear_actuator]
        name:
          type: string
        containerSpace:
          type: number
          description: Space occupied in equivalent earth containers.
        currentTask:
          type: string
          nullable: true
        taskProgressPercent:
          type: number
          minimum: 0
          maximum: 100
        location:
          $ref: '#/components/schemas/MannyLocation'
        cargo:
          $ref: '#/components/schemas/MannyCargo'
        container:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/StorageContainerSummary'
        metadata:
          type: object
          additionalProperties: true
    ProbeInventoryItemTask:
      type: object
      required: [id, type, name, currentTask, taskProgressPercent]
      properties:
        id:
          type: string
        type:
          type: string
          enum: [atomic_3d_printer, manny, waypoint_bookmark, steel_bar, steel_plate, additional_container, micro_conductor, ceramic_insulator, crystal_substrate, dopant_matrix, integrated_circuit, electric_motor, battery_pack, linear_actuator]
        name:
          type: string
        currentTask:
          type: string
          nullable: true
        taskProgressPercent:
          type: number
          minimum: 0
          maximum: 100
        location:
          $ref: '#/components/schemas/MannyLocation'
        cargo:
          $ref: '#/components/schemas/MannyCargo'
        container:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/StorageContainerSummary'
        metadata:
          type: object
          additionalProperties: true
    MannyRenameRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 40
    MannyRepairRequest:
      type: object
      required: [integrityPercent]
      properties:
        integrityPercent:
          type: number
          minimum: 0
          exclusiveMinimum: true
          maximum: 100
          description: Integrity percentage to restore. Each percent takes 10 real minutes and consumes 0.01 containers of metals.
        percent:
          type: number
          minimum: 0
          exclusiveMinimum: true
          maximum: 100
          deprecated: true
          description: Legacy alias for integrityPercent.
    MannyMineRequest:
      type: object
      required: [objectId, targetAmount]
      oneOf:
        - required: [objectId, resources, targetAmount]
        - required: [objectId, resource, targetAmount]
      properties:
        objectId:
          type: string
        resources:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: '#/components/schemas/MineableResourceType'
          description: Resource categories to mine, chosen from the target object's resourceTypes. The extracted amount is split according to resourceComposition, normalized over the selected categories.
        resource:
          allOf:
            - $ref: '#/components/schemas/MineableResourceType'
          deprecated: true
          description: Legacy single resource selector. Prefer resources.
        targetAmount:
          type: number
          minimum: 0
          exclusiveMinimum: true
          description: Amount to mine in equivalent earth containers. Manny cargo capacity is 0.05 per trip.
    MannyCraftRequest:
      type: object
      required: [recipe]
      properties:
        recipe:
          type: string
          enum: [waypoint_bookmark, steel_bar, steel_plate, additional_container, electric_motor, battery_pack, linear_actuator, manny]
          description: >
            Crafting recipe to start. Manny craftable items can be jettisoned
            later, except additional containers whose jettison mechanic is
            reserved for a later rule.
    AtomicPrinterCraftRequest:
      type: object
      required: [recipe]
      properties:
        recipe:
          type: string
          enum: [micro_conductor, ceramic_insulator, crystal_substrate, dopant_matrix, integrated_circuit]
          description: Atomic-printer recipe to start. An available Manny aboard the probe is automatically reserved as assistant.
    MannySalvageRequest:
      type: object
      required: [objectId]
      properties:
        objectId:
          type: string
          description: >
            Current-sector object id to recover. Abandoned Manny objects and
            drifting crafted-item stacks are recoverable. Visible detached
            storage containers are also recoverable and keep their hidden
            contents out of sector observations. For item stacks, a Manny
            reserves at most one cargo trip of items.
    MannyDetachStorageContainerRequest:
      type: object
      required: [containerId, mode]
      properties:
        containerId:
          type: string
          description: Additional storage container id. probe-core is not detachable.
          example: container-itm_extra
        mode:
          type: string
          enum: [drifting, hidden_on_asteroid]
        objectId:
          type: string
          description: Required when mode is hidden_on_asteroid; must be an asteroid id in the current sector.
    MannyInspectAsteroidRequest:
      type: object
      required: [objectId]
      properties:
        objectId:
          type: string
          description: Current-sector asteroid id to inspect.
    MannyRecoverStorageContainerRequest:
      type: object
      required: [objectId]
      properties:
        objectId:
          type: string
          description: Detached container object id returned by observation or hidden-object detection.
        source:
          type: string
          enum: [drifting, asteroid]
          description: Optional hint for clients; recovery is resolved by objectId.
    ArtificialObjectDetection:
      type: object
      required: [type, detection]
      properties:
        type:
          type: string
          enum: [detached_storage_container]
        detection:
          type: string
          enum: [hidden_on_asteroid]
        objectId:
          type: string
          description: Recoverable detached container id when detection yields one.
    MannyMiningTask:
      type: object
      required: [objectId, resourceType, resourceTypes, targetAmount, depositedAmount, depositedResources, extractedAmount, extractedResources, availableResources, resourceComposition, resourceProfile, target]
      properties:
        objectId:
          type: string
          description: Sector object being mined.
        resourceType:
          allOf:
            - $ref: '#/components/schemas/MineableResourceType'
          description: Legacy first selected resource; prefer resourceTypes.
        resourceTypes:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: '#/components/schemas/MineableResourceType'
        targetAmount:
          type: number
          minimum: 0
          exclusiveMinimum: true
          description: Requested total amount in equivalent earth containers.
        depositedAmount:
          type: number
          minimum: 0
          description: Amount already delivered to the probe.
        depositedResources:
          anyOf:
            - $ref: '#/components/schemas/ResourceAmountMap'
            - type: array
              maxItems: 0
        extractedAmount:
          type: number
          minimum: 0
          description: Amount already extracted from the target.
        extractedResources:
          anyOf:
            - $ref: '#/components/schemas/ResourceAmountMap'
            - type: array
              maxItems: 0
        availableResources:
          type: array
          description: Resource categories currently available on the target after existing Manny reservations.
          items:
            $ref: '#/components/schemas/MineableResourceType'
        resourceComposition:
          $ref: '#/components/schemas/ResourceComposition'
        resourceProfile:
          allOf:
            - $ref: '#/components/schemas/ResourceComposition'
          description: Normalized split applied to the selected resourceTypes for this order.
        target:
          $ref: '#/components/schemas/MiningTarget'
        artificialObjectDetected:
          $ref: '#/components/schemas/ArtificialObjectDetection'
    Manny:
      type: object
      required: [id, name, location, currentTask, taskProgressPercent, taskEstimatedEndTime, task, cargo, canReceiveOrders]
      properties:
        id:
          type: string
          description: Stable generated Manny uid used by API calls.
          example: mny_8d4a2c1f5a91b334
        name:
          type: string
          example: manny-1
        location:
          $ref: '#/components/schemas/MannyLocation'
        currentTask:
          type: string
          nullable: true
          enum: [repair, mining, crafting, assisting_atomic_printer, salvage, installing_waypoint_bookmark, detaching_storage_container, inspecting_asteroid, returning, waiting_for_space, moving_stockage]
        taskProgressPercent:
          type: number
          minimum: 0
          maximum: 100
        taskEstimatedEndTime:
          type: string
          format: date-time
          nullable: true
          description: Estimated ISO-8601 completion time for the current task, or null when idle.
        task:
          description: >
            Current task payload. When currentTask is mining, this payload has
            the MannyMiningTask shape, including target.resourceAmounts for
            asteroids and resourceProfile for the selected split. Idle Mannys
            may expose an empty payload.
          anyOf:
            - $ref: '#/components/schemas/MannyMiningTask'
            - type: object
              additionalProperties: true
            - type: array
              maxItems: 0
        cargo:
          $ref: '#/components/schemas/MannyCargo'
        canReceiveOrders:
          type: boolean
    MannyLocation:
      type: object
      required: [type]
      properties:
        type:
          type: string
          enum: [probe, sector]
        sector:
          type: object
          nullable: true
          properties:
            relative:
              $ref: '#/components/schemas/Vector'
    MannyCargo:
      type: object
      required: [capacity, deuterium, metals, ice, organicCompounds, capacityUnit]
      properties:
        capacity:
          type: number
          example: 0.05
        deuterium:
          type: number
        metals:
          type: number
        ice:
          type: number
        organicCompounds:
          type: number
        capacityUnit:
          type: string
          enum: [earth_container_equivalent]
    SectorSummary:
      type: object
      required: [coordinates, objects]
      properties:
        coordinates:
          type: object
          properties:
            relative:
              $ref: '#/components/schemas/Vector'
        objects:
          type: array
          description: Detailed current or visited sector objects. Abandoned or forgotten Manny objects are included only when the observed sector is the probe current sector.
          items:
            $ref: '#/components/schemas/SectorObject'
    SectorObservation:
      type: object
      required: [relativeCoordinates, distance, knowledgeLevel, confidence, scan]
      properties:
        relativeCoordinates:
          $ref: '#/components/schemas/Vector'
        distance:
          type: integer
        knowledgeLevel:
          type: string
          enum: [detailed, neighbor_scan, distant_scan, long_range_estimation]
        confidence:
          type: number
          minimum: 0
          maximum: 1
        objects:
          type: array
          description: Detailed current or visited sector objects. Abandoned or forgotten Manny objects are included only when the observed sector is the probe current sector.
          items:
            $ref: '#/components/schemas/SectorObject'
        probes:
          type: array
          description: Other probes detected in the current sector. Present only on current-sector observations when at least one other probe is detected.
          items:
            $ref: '#/components/schemas/SectorProbePresence'
        estimatedObjects:
          type: object
          additionalProperties: true
        possibleObjects:
          type: array
          items:
            type: string
        navigationalRisk:
          type: string
        message:
          type: string
        sensorMode:
          type: string
          enum: [normal, degraded, blind]
        dataFreshness:
          type: string
          enum: [live, degraded_live, historical, unavailable]
        scan:
          type: object
          required: [currentSectorResidenceSeconds, requiredResidenceSeconds, scanQuality]
          properties:
            currentSectorResidenceSeconds:
              type: integer
            requiredResidenceSeconds:
              type: integer
            scanQuality:
              type: number
              minimum: 0
              maximum: 1
    SectorProbePresence:
      type: object
      required: [id, name, moving]
      properties:
        id:
          type: integer
        name:
          type: string
        moving:
          type: boolean
    SectorObject:
      type: object
      required: [type, name, summary]
      properties:
        id:
          type: string
        type:
          type: string
          enum: [star, planet, asteroid, dust_cloud, black_hole, solar_system, manny, drifting_item, detached_container]
        name:
          type: string
          nullable: true
        estimated:
          type: boolean
        summary:
          type: string
        mass:
          type: number
        massUnit:
          type: string
          enum: [solar_mass, earth_mass]
          description: Unit used by mass when present. Stars, black holes and dust clouds use solar_mass; planets and asteroids use earth_mass.
        radius:
          type: number
        radiusUnit:
          type: string
          enum: [solar_radius, earth_radius, kilometer, astronomical_unit]
          description: Unit used by radius when present. Stars use solar_radius; planets and asteroids use earth_radius; black holes use kilometer; solar systems and dust clouds use astronomical_unit.
        dangerLevel:
          type: string
          enum: [low, moderate, extreme]
        starCount:
          type: integer
          minimum: 0
          description: Present on solar systems.
        planetCount:
          type: integer
          minimum: 0
          description: Present on solar systems.
        orbitalBodyCount:
          type: integer
          minimum: 0
          description: Present on solar systems.
        resources:
          $ref: '#/components/schemas/ResourceHintList'
        resourceTypes:
          type: array
          description: Present on planets and asteroids; use these values in MannyMineRequest.resources.
          items:
            $ref: '#/components/schemas/MineableResourceType'
        resourceComposition:
          $ref: '#/components/schemas/ResourceComposition'
        resourceAmounts:
          $ref: '#/components/schemas/ResourceAmountMap'
        mannyMineable:
          type: boolean
          description: Present on planets and asteroids; true when a Manny can mine this object.
        composition:
          type: string
          description: Present on asteroids; generator composition class such as iron, silicate, carbonaceous, ice or rare_metals.
        category:
          type: string
          description: Present on planets.
        salvageable:
          type: boolean
          description: True when the object can be recovered with POST /api/probe/mannies/{mannyId}/salvage.
        waypointBookmarks:
          type: array
          items:
            $ref: '#/components/schemas/WaypointBookmarkHistory'
        bookmarkTargets:
          type: array
          description: Present on solar systems; lists nested stars and orbital bodies that can receive a waypoint bookmark.
          items:
            $ref: '#/components/schemas/WaypointBookmarkTarget'
        minableTargets:
          type: array
          description: Present on solar systems; lists nested planets/asteroids that can be mined by a Manny.
          items:
            $ref: '#/components/schemas/MiningTarget'
        mannyState:
          type: string
          enum: [abandoned, forgotten]
          description: Present only for detected Manny objects in the current sector.
        mannyUid:
          type: string
          description: Present only for detected Manny objects.
        cargo:
          $ref: '#/components/schemas/MannyCargo'
        itemType:
          type: string
          enum: [waypoint_bookmark, steel_bar, steel_plate, micro_conductor, ceramic_insulator, crystal_substrate, dopant_matrix, integrated_circuit, electric_motor, battery_pack, linear_actuator]
          description: Present only for drifting item stacks.
        quantity:
          type: integer
          description: Present only for drifting item stacks.
        containerSpace:
          type: number
          description: Per-item storage space for drifting item stacks.
        mode:
          type: string
          enum: [drifting, hidden_on_asteroid]
          description: Present only for detached containers. Hidden containers are not included in normal observations.
        targetObjectId:
          type: string
          description: Present only for detached containers that are attached to another sector object.
        capacity:
          type: number
          description: Storage capacity for detached container objects.
        capacityUnit:
          type: string
          enum: [earth_container_equivalent]
    WaypointBookmarkTarget:
      type: object
      required: [id, type, name]
      properties:
        id:
          type: string
        type:
          type: string
          enum: [star, planet, asteroid, dust_cloud, black_hole, solar_system]
        name:
          type: string
          nullable: true
        mass:
          type: number
        massUnit:
          type: string
          enum: [solar_mass, earth_mass]
        radius:
          type: number
        radiusUnit:
          type: string
          enum: [solar_radius, earth_radius, kilometer, astronomical_unit]
        waypointBookmarks:
          type: array
          items:
            $ref: '#/components/schemas/WaypointBookmarkHistory'
    WaypointBookmarkHistory:
      type: object
      required: [name, playerId, playerName, createdAt]
      properties:
        name:
          type: string
        playerId:
          type: integer
        playerName:
          type: string
        createdAt:
          type: string
          format: date-time
