feat: add world voxel snapshot forwarding

Collect chunk voxel data from the bot world and stream snapshot,
chunk, and unwatch messages through the forward protocol.
This commit is contained in:
2026-10-23 10:44:37 +08:00
parent 0acef64f48
commit 6c69afb38a
11 changed files with 1997 additions and 18 deletions
+34 -1
View File
@@ -1,4 +1,4 @@
import type {EnsureForward, ForwardAck, ForwardErrorCode, JoinReady, RelayRequest, WorkflowPayloads} from '../types/forward.ts';
import type {EnsureForward, ForwardAck, ForwardErrorCode, JoinReady, RelayRequest, WorkflowPayloads, WorldPayloads} from '../types/forward.ts';
import {assert_json} from '../bot/workflow/runtime/value.ts';
const errors: Record<ForwardErrorCode, [string, boolean]> = {
@@ -112,6 +112,11 @@ export function validate_rpc(value: unknown): RelayRequest {
|| !uuid(value.request_id) || !uuid(value.correlation_id) || !uuid(value.forward_session_id)
|| !integer(value.deadline_at, 1)) throw new ForwardFailure('INVALID_REQUEST');
switch (value.method) {
case 'world.snapshot':
case 'world.chunk':
case 'world.unwatch':
validate_world_payload(value.method, value.payload);
break;
case 'workflow.start':
case 'workflow.stop':
case 'workflow.snapshot':
@@ -217,3 +222,31 @@ export function validate_workflow_payload<M extends keyof WorkflowPayloads>(meth
}
return payload as WorkflowPayloads[M];
}
export function validate_world_payload<M extends keyof WorldPayloads>(method: M, payload: unknown): WorldPayloads[M] {
if (method === 'world.unwatch') {
if (!exact(payload, ['watch_id']) || !uuid(payload.watch_id)) throw new ForwardFailure('INVALID_REQUEST');
} else if (method === 'world.chunk') {
if (!exact(payload, ['x', 'z', 'world_generation', 'watch_id'])
|| !Number.isSafeInteger(payload.x) || !Number.isSafeInteger(payload.z)
|| !uuid(payload.world_generation) || !uuid(payload.watch_id)) throw new ForwardFailure('INVALID_REQUEST');
} else if (method === 'world.snapshot') {
if (!exact(payload, ['radius', 'known'], ['world_generation', 'watch_id'])
|| !integer(payload.radius, 1) || payload.radius > 3
|| Object.hasOwn(payload, 'world_generation') && !uuid(payload.world_generation)
|| Object.hasOwn(payload, 'watch_id') && !uuid(payload.watch_id)
|| !Array.isArray(payload.known) || payload.known.length > 49
|| Reflect.ownKeys(payload.known).length !== payload.known.length + 1) throw new ForwardFailure('INVALID_REQUEST');
const seen = new Set<string>();
for (let index = 0; index < payload.known.length; index++) {
const descriptor = Object.getOwnPropertyDescriptor(payload.known, String(index));
const item = descriptor && 'value' in descriptor && descriptor.enumerable ? descriptor.value : undefined;
if (!exact(item, ['x', 'z', 'revision']) || !Number.isSafeInteger(item.x)
|| !Number.isSafeInteger(item.z) || !uuid(item.revision)) throw new ForwardFailure('INVALID_REQUEST');
const key = `${item.x},${item.z}`;
if (seen.has(key)) throw new ForwardFailure('INVALID_REQUEST');
seen.add(key);
}
} else throw new ForwardFailure('INVALID_REQUEST');
return payload as WorldPayloads[M];
}