{"record":{"id":"e281d285b5ff6486","repo":"moeru-ai/airi","slug":"invalid-godot-stage-view-state-patch-payload","errorCode":null,"errorMessage":"Invalid Godot stage view-state patch payload.","messagePattern":"Invalid Godot stage view-state patch payload\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-shared/src/godot-stage/view-state.ts","lineNumber":97,"sourceCode":"function hasStageViewPatchMutation(patch: StageViewPatch) {\n  return hasStageViewVec3PatchMutation(patch.camera?.position)\n    || patch.camera?.yawDeg !== undefined\n    || patch.camera?.pitchDeg !== undefined\n    || patch.camera?.fovDeg !== undefined\n}\n\nexport const StageViewPatchSchema = pipe(\n  strictObject({\n    camera: optional(StageCameraPosePatchSchema),\n  }),\n  check(hasStageViewPatchMutation, 'View patch must include at least one field.'),\n)\n\n/** Parses a host-origin Godot view-state patch. */\nexport function parseStageViewPatchPayload(payload: unknown): StageViewPatch {\n  const result = safeParse(StageViewPatchSchema, payload)\n  if (!result.success)\n    throw new Error('Invalid Godot stage view-state patch payload.')\n\n  return result.output\n}\n\n/** Reason attached to a Godot view-state snapshot event. */\nexport type StageViewSnapshotReason\n  = | 'loaded'\n    | 'remote-patch'\n    | 'local-input'\n    | 'request'\n    | 'shutdown-flush'\n\n/** Runtime-only avatar bounds emitted with view snapshots for UI range decisions. */\nexport interface StageAvatarBoundsPayload {\n  center: StageViewVec3\n  size: StageViewVec3\n  maxDimension: number\n}","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-shared/src/godot-stage/view-state.ts#L79-L115","documentation":"Thrown by parseStageViewPatchPayload when the incoming payload fails safeParse against StageViewPatchSchema. The schema is a strictObject that allows only an optional `camera` (StageCameraPosePatchSchema), and a check() requires at least one field to be present — so an empty object {}, unknown keys, non-object payloads, or a malformed camera pose all fail. It guards the host-origin boundary of the Godot stage view-state protocol.","triggerScenarios":"Sending {}, null, a JSON string (not yet parsed), an object with typo'd keys like {\"cameraa\": {...}}, or a camera patch missing required pose fields to the patch ingestion path that calls parseStageViewPatchPayload.","commonSituations":"Version skew between the host package and the Godot plugin where one side adds/renames fields; hand-crafting IPC or HTTP payloads in tests without consulting the schema; double-encoding the payload (JSON.stringify twice) so it arrives as a string; forwarding a snapshot payload where a patch was expected.","solutions":["Log the safeParse issues next to the generic error: const r = safeParse(StageViewPatchSchema, payload); if (!r.success) report r.issues — the issues pinpoint the offending field","Ensure the payload is a parsed object of shape { camera: { ...StageCameraPosePatch fields } } and contains no extra top-level keys","Align versions of packages/stage-shared between the host and the Godot stage process after protocol changes","Reproduce the exact wire payload in a unit test and iterate until safeParse succeeds before touching integration code"],"exampleFix":"// before\nconst patch = parseStageViewPatchPayload(raw) // opaque 'Invalid ... patch payload.'\n\n// after\nimport { safeParse, StageViewPatchSchema, parseStageViewPatchPayload } from '../godot-stage/view-state'\nconst checked = safeParse(StageViewPatchSchema, raw)\nif (!checked.success) {\n  throw new Error(`View patch rejected: ${JSON.stringify(checked.issues.map(i => [i.path, i.message]))}`)\n}\nconst patch = parseStageViewPatchPayload(raw)","handlingStrategy":"validation","validationCode":"import { safeParse, StageViewPatchSchema } from '../godot-stage/view-state'\n\nconst checked = safeParse(StageViewPatchSchema, payload)\nif (!checked.success) {\n  log.warn('view patch rejected', checked.issues)\n  return\n}\nconst patch = parseStageViewPatchPayload(payload)","typeGuard":"const isStageViewPatchInput = (p: unknown): boolean => safeParse(StageViewPatchSchema, p).success","tryCatchPattern":"catch (e) { if (e.message.includes('view-state patch payload')) { log.warn('dropping malformed patch', payload); return } throw e }","preventionTips":["Always safeParse external payloads first and log r.issues for the exact path","Keep packages/stage-shared and the Godot plugin versions locked together","Add a Vitest fixture of the real wire payload to catch protocol drift"],"tags":["valibot","schema-validation","godot","ipc-payload","strict-object"],"backgroundTag":"schema-validation-failed","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}