import { LIMITS } from '../shared/world-rules.ts' import { agentPlayer, getCaller } from './identity.ts' import { WorldError, applyBlockOps, ownedBuild, parseBlockOp, requireUuid, sleep, type BlockOp, } from './world-ops.ts' export async function requireAgent(agentName: unknown) { const caller = await getCaller() if (!caller) throw new WorldError('Sign in to Kody to build.', 401) return { caller, player: agentPlayer(caller, agentName) } } /** * Stream block writes for a build in small batches so every viewer sees the * structure appear block-by-block. */ export async function streamBuildOps(input: { buildId: unknown blocks: unknown kind: 'put' | 'del' batchSize?: unknown delayMs?: unknown }) { const caller = await getCaller() if (!caller) throw new WorldError('Sign in to Kody to build.', 401) const buildId = requireUuid(input.buildId, 'buildId') const build = await ownedBuild(buildId, caller.username) if (build.status === 'done' || build.status === 'failed') { throw new WorldError('That build is finished. Start a new build.', 409) } if (!Array.isArray(input.blocks)) throw new WorldError('blocks must be an array of {x, y, z, material}.') if (input.blocks.length > LIMITS.maxAgentBlocksPerCall) { throw new WorldError( `At most ${LIMITS.maxAgentBlocksPerCall} blocks per call. Split the build into several calls.`, ) } const ops: Array = [] let rejected = 0 for (const raw of input.blocks) { const op = parseBlockOp(raw, input.kind) if (op) ops.push(op) else rejected++ } const batchSize = clampInt(input.batchSize, 1, 60, 24) const delayMs = clampInt(input.delayMs, 0, 1000, 150) let placed = 0 let removed = 0 let batches = 0 for (let i = 0; i < ops.length; i += batchSize) { const chunk = ops.slice(i, i + batchSize) const out = await applyBlockOps({ player: build.player, worldId: build.worldId, ops: chunk, buildId: input.kind === 'put' ? buildId : null, }) placed += out.placed removed += out.removed batches++ if (delayMs && i + batchSize < ops.length) await sleep(delayMs) } return { buildId, placed, removed, rejected, batches } } function clampInt(value: unknown, min: number, max: number, fallback: number) { const n = Number(value) if (!Number.isFinite(n)) return fallback return Math.max(min, Math.min(max, Math.round(n))) }