fix(navigation): block desk_cubicle in nav grid with zero padding to prevent walk-through (#75)
- desk_cubicle now has blocksNavigation: true with navPadding: 0 (tight blocking, no inflation — aisles stay clear) - buildNavGrid reads per-item navPadding from ITEM_METADATA - getDeskLocations targets y-5 (chair position, above desk blocked zone) - Agents route AROUND desks instead of through them - Chair stays passable so agents can reach their sitting position Fixes object passthrough for desks. Other large passable items (doors, lamps) unchanged — they remain non-blocking by design. Co-authored-by: Neo (subagent) <neo@openclaw.local> Co-authored-by: Luke The Dev <252071647+iamlukethedev@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
4b7b295846
commit
464a49bb6d
@@ -4500,7 +4500,7 @@ export function RetroOffice3D({
|
|||||||
const agent = renderAgentLookupRef.current.get(agentId);
|
const agent = renderAgentLookupRef.current.get(agentId);
|
||||||
if (!agent) return;
|
if (!agent) return;
|
||||||
const tx = item.x + 40;
|
const tx = item.x + 40;
|
||||||
const ty = item.y + 40;
|
const ty = item.y - 5;
|
||||||
const path = planPath(agent.x, agent.y, tx, ty);
|
const path = planPath(agent.x, agent.y, tx, ty);
|
||||||
// Mutate the render agent ref directly so the tick loop picks it up.
|
// Mutate the render agent ref directly so the tick loop picks it up.
|
||||||
Object.assign(agent, {
|
Object.assign(agent, {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const getItemBaseSize = (item: FurnitureItem) => {
|
|||||||
* This is the single source of truth for nav-blocking behaviour. `buildNavGrid` in
|
* This is the single source of truth for nav-blocking behaviour. `buildNavGrid` in
|
||||||
* navigation.ts reads this instead of maintaining its own hardcoded type set.
|
* navigation.ts reads this instead of maintaining its own hardcoded type set.
|
||||||
*/
|
*/
|
||||||
export const ITEM_METADATA: Record<string, { blocksNavigation: boolean }> = {
|
export const ITEM_METADATA: Record<string, { blocksNavigation: boolean; navPadding?: number }> = {
|
||||||
// ── structural ────────────────────────────────────────────────────────────
|
// ── structural ────────────────────────────────────────────────────────────
|
||||||
wall: { blocksNavigation: true },
|
wall: { blocksNavigation: true },
|
||||||
door: { blocksNavigation: false }, // passable
|
door: { blocksNavigation: false }, // passable
|
||||||
@@ -117,7 +117,7 @@ export const ITEM_METADATA: Record<string, { blocksNavigation: boolean }> = {
|
|||||||
couch_v: { blocksNavigation: true },
|
couch_v: { blocksNavigation: true },
|
||||||
beanbag: { blocksNavigation: true }, // large floor seat (issue #4)
|
beanbag: { blocksNavigation: true }, // large floor seat (issue #4)
|
||||||
// ── desks / workstations ──────────────────────────────────────────────────
|
// ── desks / workstations ──────────────────────────────────────────────────
|
||||||
desk_cubicle: { blocksNavigation: false }, // agents stand at these; collision handled separately
|
desk_cubicle: { blocksNavigation: true, navPadding: 0 }, // blocks nav with zero padding (tight to desk body)
|
||||||
executive_desk: { blocksNavigation: true },
|
executive_desk: { blocksNavigation: true },
|
||||||
// ── tables ────────────────────────────────────────────────────────────────
|
// ── tables ────────────────────────────────────────────────────────────────
|
||||||
round_table: { blocksNavigation: true },
|
round_table: { blocksNavigation: true },
|
||||||
|
|||||||
@@ -84,14 +84,15 @@ const itemBlocksNavigation = (type: string): boolean =>
|
|||||||
|
|
||||||
export function buildNavGrid(furniture: FurnitureItem[]): NavGrid {
|
export function buildNavGrid(furniture: FurnitureItem[]): NavGrid {
|
||||||
const grid = new Uint8Array(GRID_COLS * GRID_ROWS);
|
const grid = new Uint8Array(GRID_COLS * GRID_ROWS);
|
||||||
const pad = GRID_CELL * 0.6;
|
const defaultPad = GRID_CELL * 0.6;
|
||||||
for (const item of furniture) {
|
for (const item of furniture) {
|
||||||
if (!itemBlocksNavigation(item.type)) continue;
|
if (!itemBlocksNavigation(item.type)) continue;
|
||||||
|
const itemPad = ITEM_METADATA[item.type]?.navPadding ?? defaultPad;
|
||||||
const bounds = getItemBounds(item);
|
const bounds = getItemBounds(item);
|
||||||
const x1 = bounds.x - pad;
|
const x1 = bounds.x - itemPad;
|
||||||
const y1 = bounds.y - pad;
|
const y1 = bounds.y - itemPad;
|
||||||
const x2 = bounds.x + bounds.w + pad;
|
const x2 = bounds.x + bounds.w + itemPad;
|
||||||
const y2 = bounds.y + bounds.h + pad;
|
const y2 = bounds.y + bounds.h + itemPad;
|
||||||
const c1 = Math.max(0, Math.floor(x1 / GRID_CELL));
|
const c1 = Math.max(0, Math.floor(x1 / GRID_CELL));
|
||||||
const c2 = Math.min(GRID_COLS - 1, Math.floor(x2 / GRID_CELL));
|
const c2 = Math.min(GRID_COLS - 1, Math.floor(x2 / GRID_CELL));
|
||||||
const r1 = Math.max(0, Math.floor(y1 / GRID_CELL));
|
const r1 = Math.max(0, Math.floor(y1 / GRID_CELL));
|
||||||
@@ -302,7 +303,7 @@ export function astar(
|
|||||||
export const getDeskLocations = (items: FurnitureItem[]) =>
|
export const getDeskLocations = (items: FurnitureItem[]) =>
|
||||||
items
|
items
|
||||||
.filter((item) => item.type === "desk_cubicle")
|
.filter((item) => item.type === "desk_cubicle")
|
||||||
.map((item) => ({ x: item.x + 40, y: item.y + 40 }));
|
.map((item) => ({ x: item.x + 40, y: item.y - 5 }));
|
||||||
|
|
||||||
export const getMeetingSeatLocations = (items: FurnitureItem[]) => {
|
export const getMeetingSeatLocations = (items: FurnitureItem[]) => {
|
||||||
// Meeting seats are inferred from chair placement in the conference area so standup
|
// Meeting seats are inferred from chair placement in the conference area so standup
|
||||||
@@ -497,7 +498,7 @@ export const resolveDeskIndexForItem = (
|
|||||||
if (deskLocations.length === 0) return -1;
|
if (deskLocations.length === 0) return -1;
|
||||||
if (item.type === "desk_cubicle") {
|
if (item.type === "desk_cubicle") {
|
||||||
return deskLocations.findIndex(
|
return deskLocations.findIndex(
|
||||||
(desk) => desk.x === item.x + 40 && desk.y === item.y + 40,
|
(desk) => desk.x === item.x + 40 && desk.y === item.y - 5,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,17 +203,19 @@ describe("buildNavGrid + astar – full pathfinding integration (issue #4)", ()
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("buildNavGrid – specific non-blocking item types (issue #4)", () => {
|
describe("buildNavGrid – specific non-blocking item types (issue #4)", () => {
|
||||||
it("desk_cubicle does NOT block navigation — agents stand at these", () => {
|
it("desk_cubicle blocks navigation with zero padding — agents route around desks", () => {
|
||||||
/*
|
/*
|
||||||
* desk_cubicle has blocksNavigation: false in ITEM_METADATA.
|
* desk_cubicle has blocksNavigation: true with navPadding: 0 in ITEM_METADATA.
|
||||||
* Agents interact with desks by standing beside them; the desk itself
|
* The desk body is blocked in the nav grid so agents route around it,
|
||||||
* is not a solid blocker in the pathfinding grid.
|
* but zero padding keeps aisles between desks navigable.
|
||||||
|
* Agents sit at the chair position (y - 5), which is above the blocked zone.
|
||||||
*/
|
*/
|
||||||
const item = makeItem("desk_cubicle", 400, 300);
|
const item = makeItem("desk_cubicle", 400, 300);
|
||||||
const grid = buildNavGrid([item]);
|
const grid = buildNavGrid([item]);
|
||||||
expect(isBlocked(grid, 400, 300)).toBe(false);
|
expect(isBlocked(grid, 400, 300)).toBe(true);
|
||||||
// Confirm via metadata as the authoritative source.
|
// Confirm via metadata as the authoritative source.
|
||||||
expect(ITEM_METADATA["desk_cubicle"]?.blocksNavigation).toBe(false);
|
expect(ITEM_METADATA["desk_cubicle"]?.blocksNavigation).toBe(true);
|
||||||
|
expect(ITEM_METADATA["desk_cubicle"]?.navPadding).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("door does NOT block navigation — agents must be able to walk through doors", () => {
|
it("door does NOT block navigation — agents must be able to walk through doors", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user