diff --git a/server/webclient/src/lite/LiteClient.ts b/server/webclient/src/lite/LiteClient.ts index 99820c594..088556cf4 100644 --- a/server/webclient/src/lite/LiteClient.ts +++ b/server/webclient/src/lite/LiteClient.ts @@ -632,11 +632,21 @@ export class LiteClient { /** Send a MOVE_GAMECLICK with a client-computed path. See movement.ts. */ walkTo(worldX: number, worldZ: number, running = false): boolean { - const dest = this.toSceneTile(worldX, worldZ); - if (!dest || !this.localPlayer) { + if (!this.localPlayer) { return false; } - return findPathToTile(this, dest.x, dest.z, running); + + // Match Client.walkTo: a long-distance BotActions route can hand the + // client a global waypoint beyond its current 104x104 build area. Walk + // to the corresponding scene edge; crossing it causes REBUILD_NORMAL, + // after which BotActions observes progress and dispatches the next leg. + // Keeping the one-tile border excluded also matches the browser's + // collision bounds (tiles 0 and 103 are the blocked border ring). + const minBound = 1; + const maxBound = BuildArea.SIZE - 2; + const destX = Math.max(minBound, Math.min(maxBound, worldX - this.mapBuildBaseX)); + const destZ = Math.max(minBound, Math.min(maxBound, worldZ - this.mapBuildBaseZ)); + return findPathToTile(this, destX, destZ, running); } // ------------------------------------------------------ outgoing plumbing diff --git a/server/webclient/src/lite/action-encoding.test.ts b/server/webclient/src/lite/action-encoding.test.ts index 20c13d7c8..bc9fc7f9c 100644 --- a/server/webclient/src/lite/action-encoding.test.ts +++ b/server/webclient/src/lite/action-encoding.test.ts @@ -19,6 +19,7 @@ import { beforeAll, describe, expect, test } from 'bun:test'; import './dom-shim.js'; +import CollisionMap from '#/dash3d/CollisionMap.js'; import { ClientProt } from '#/io/ClientProt.js'; import Packet from '#/io/Packet.js'; import { ServerProt } from '#/io/ServerProt.js'; @@ -251,4 +252,28 @@ describe('outgoing action encoding', () => { expect(r.g2()).toBe(9); expect(r.g2()).toBe(INV_COMPONENT); }); + + test('long walks clamp to the current scene edge like the browser client', () => { + if (!available) { + return; + } + + const c = makeClient('bota'); + c.mapBuildBaseX = 3200; + c.mapBuildBaseZ = 3200; + c.collision[0] = new CollisionMap(); + c.localPlayer!.routeX[0] = 50; + c.localPlayer!.routeZ[0] = 50; + + const start = c.out.pos; + expect(c.walkTo(4000, 3250, true)).toBe(true); + + const r = new Capture(c.out, start); + expect(r.g1()).toBe(ClientProt.MOVE_GAMECLICK); + expect(r.g1()).toBe(5); // one turn: run flag + absolute x/z + expect(r.g1()).toBe(1); // running + expect(r.g2()).toBe(3302); // baseX + build-area max interior tile + expect(r.g2()).toBe(3250); + expect(r.at).toBe(c.out.pos); + }); }); diff --git a/server/webclient/src/lite/runner.ts b/server/webclient/src/lite/runner.ts index 4a108833e..1e56d854e 100644 --- a/server/webclient/src/lite/runner.ts +++ b/server/webclient/src/lite/runner.ts @@ -12,7 +12,6 @@ import { startSession, type LiteSession } from './session.js'; import { type ActionResult } from '#/bot/ActionExecutor.js'; -import { formatWorldStateForAgent } from '#/bot/formatters.js'; import { BotActionQueue, type QueuedBotAction } from '#/bot/ActionQueue.js'; import type { BotAction } from '#/bot/types.js'; import type { LiteClient } from './LiteClient.js'; @@ -223,11 +222,7 @@ class LiteGatewayRunner { if (!this.wsConnected) return; const state = this.client.collectBotState(this.serverTick); if (!state) return; - this.send({ - type: 'state', - state, - formattedState: formatWorldStateForAgent(state, 'SDK Control') - }); + this.send({ type: 'state', state }); } }