moeru-ai/airi · error · Error

Invalid Godot stage view-state snapshot payload.

Error message

Invalid Godot stage view-state snapshot payload.

What it means

Thrown by parseStageViewSnapshotPayload when a Godot-emitted snapshot fails safeParse against StageViewSnapshotPayloadSchema. The schema requires `state` (full StageViewStateSchema), a `reason` that must be one of 'loaded' | 'remote-patch' | 'local-input' | 'request' | 'shutdown-flush', an optional `avatarBounds`, and a `requestId` — all under a strictObject that rejects unknown or missing keys. It protects the host from malformed snapshots coming over the Godot bridge.

Source

Thrown at packages/stage-shared/src/godot-stage/view-state.ts:143

  state: StageViewState
  reason: StageViewSnapshotReason
  /** Runtime-only avatar bounds. This is not persisted Godot view state. */
  avatarBounds?: StageAvatarBoundsPayload
  requestId?: string
}

export const StageViewSnapshotPayloadSchema = strictObject({
  state: StageViewStateSchema,
  reason: picklist(['loaded', 'remote-patch', 'local-input', 'request', 'shutdown-flush']),
  avatarBounds: optional(StageAvatarBoundsPayloadSchema),
  requestId: requestIdSchema,
})

/** Parses a Godot-emitted view-state snapshot. */
export function parseStageViewSnapshotPayload(payload: unknown): StageViewSnapshotPayload {
  const result = safeParse(StageViewSnapshotPayloadSchema, payload)
  if (!result.success)
    throw new Error('Invalid Godot stage view-state snapshot payload.')

  return {
    state: result.output.state,
    reason: result.output.reason,
    ...(result.output.avatarBounds ? { avatarBounds: result.output.avatarBounds } : {}),
    ...(result.output.requestId != null ? { requestId: result.output.requestId } : {}),
  }
}

/** Stable machine-readable Godot view-state error code. */
export type StageViewErrorCode
  = | 'invalid-payload'
    | 'invalid-state-file'
    | 'persistence-failed'
    | 'storage-root-missing'
    | 'view-state-unavailable'

/** Error event emitted by Godot for view-state request, validation, or lifecycle failures. */

View on GitHub (pinned to 677329427f)

Solutions

  1. Log safeParse issues (r.issues includes the exact path, e.g. ['reason']) before or instead of relying on the wrapped message
  2. Compare the emitted reason strings in the Godot source with the picklist in view-state.ts and rebuild/align both sides
  3. Add a regression Vitest case that feeds the real captured payload (from a log) through StageViewSnapshotPayloadSchema
  4. If a new reason is legitimately added on the Godot side, extend the picklist and StageViewSnapshotReason in the same change

Example fix

// before
const snap = parseStageViewSnapshotPayload(event.data) // generic failure

// after
import { safeParse, StageViewSnapshotPayloadSchema } from '../godot-stage/view-state'
const r = safeParse(StageViewSnapshotPayloadSchema, event.data)
if (!r.success) {
  console.error('snapshot issues', r.issues) // shows e.g. reason: invalid option
  return
}
const snap = parseStageViewSnapshotPayload(event.data)
Defensive patterns

Strategy: validation

Validate before calling

import { safeParse, StageViewSnapshotPayloadSchema } from '../godot-stage/view-state'

const checked = safeParse(StageViewSnapshotPayloadSchema, event.data)
if (!checked.success) {
  log.error('snapshot rejected', checked.issues)
  return
}
const snap = parseStageViewSnapshotPayload(event.data)

Type guard

const isStageViewSnapshotPayload = (p: unknown): boolean => safeParse(StageViewSnapshotPayloadSchema, p).success

Try / catch

catch (e) { if (e.message.includes('view-state snapshot payload')) { requestFreshSnapshot(); return } throw e }

Prevention

When it happens

Trigger: Godot emits a snapshot with a new `reason` value not in the picklist, omits `state` or sends a state with an out-of-range camera field, includes an extra key, or the payload is double-stringified JSON.

Common situations: Updating the Godot engine side (engines/stage-tamagotchi-godot) without rebuilding packages/stage-shared, so the two sides disagree on the reason enum or state shape; a Godot script error producing a half-initialized state table; string/number type drift for camera fields between GDScript and TypeScript.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/f2c7d96a84379857. Report an issue: GitHub.