fix(issue-4): replace hardcoded BLOCKING_TYPES with metadata-driven ITEM_METADATA (#20)
* fix(issue-4): add missing solid floor props to BLOCKING_TYPES Audited all furniture types defined in furnitureDefaults.ts and geometry.ts (ITEM_FOOTPRINT) against BLOCKING_TYPES in navigation.ts. Added five previously missing solid floor props: - water_cooler: freestanding floor appliance, agents pathfound through it - server_terminal: floor-standing terminal in the server room - dishwasher: floor appliance in the kitchen area - easel: floor-standing art-room prop - beanbag: floor seat large enough to obstruct walking paths Also adds a unit test asserting every newly-added type is correctly blocked in the nav grid, and that non-solid desk decorations (keyboard) remain free. * refactor(nav): replace hardcoded BLOCKING_TYPES with metadata-driven ITEM_METADATA Previously, buildNavGrid() maintained a hardcoded BLOCKING_TYPES set in navigation.ts. The issue #4 fix added the five missing solid props directly to that set, but this approach is brittle — every new furniture type requires a separate PR touching navigation.ts to stay correct. This rework introduces ITEM_METADATA in geometry.ts (alongside the existing ITEM_FOOTPRINT record) as the single source of truth for per-type navigation properties: export const ITEM_METADATA: Record<string, { blocksNavigation: boolean }> Each item type explicitly declares blocksNavigation: true/false. The five props fixed in issue #4 (water_cooler, server_terminal, dishwasher, easel, beanbag) retain their blocking status. Unknown types default to false, so future decorative items never accidentally block navigation. Changes: - geometry.ts: add ITEM_METADATA export (64 type entries) - navigation.ts: remove BLOCKING_TYPES set; add itemBlocksNavigation() helper that reads ITEM_METADATA[type]?.blocksNavigation ?? false; update buildNavGrid() to call it - tests/unit/navigation.navBlockers.test.ts: retain original 5 solid-prop tests; add metadata-driven test suite covering: all blocking types from metadata, all non-blocking types from metadata, runtime-added type with blocksNavigation:true, and unknown-type safe fallback All 10 tests pass; lint clean; only pre-existing TS2367 (issue #13) remains. * test(navigation): add extended nav blocker tests (issue #4) Additional tests for buildNavGrid and astar pathfinding: - Adjacent blocking items create a continuous impassable wall - Blocking item near grid edge/boundary causes no out-of-bounds errors - Full pathfinding integration: astar routes AROUND a cabinet placed between start and end (not just that cells are blocked) - desk_cubicle explicitly does NOT block — confirmed via ITEM_METADATA - door explicitly does NOT block — agents must walk through doors --------- Co-authored-by: Neo (subagent) <neo@openclaw.local>
This commit is contained in:
committed by
GitHub
parent
941612ab2d
commit
e24ed41532
@@ -97,6 +97,80 @@ export const getItemBaseSize = (item: FurnitureItem) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Per-type metadata for furniture items.
|
||||
*
|
||||
* blocksNavigation: true → solid floor-standing prop; marks grid cells as impassable.
|
||||
* blocksNavigation: false → desk decoration, wall-mounted, elevated, or passable item.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
export const ITEM_METADATA: Record<string, { blocksNavigation: boolean }> = {
|
||||
// ── structural ────────────────────────────────────────────────────────────
|
||||
wall: { blocksNavigation: true },
|
||||
door: { blocksNavigation: false }, // passable
|
||||
// ── seating / lounge ──────────────────────────────────────────────────────
|
||||
chair: { blocksNavigation: false }, // passable / agents sit on them
|
||||
couch: { blocksNavigation: true },
|
||||
couch_v: { blocksNavigation: true },
|
||||
beanbag: { blocksNavigation: true }, // large floor seat (issue #4)
|
||||
// ── desks / workstations ──────────────────────────────────────────────────
|
||||
desk_cubicle: { blocksNavigation: false }, // agents stand at these; collision handled separately
|
||||
executive_desk: { blocksNavigation: true },
|
||||
// ── tables ────────────────────────────────────────────────────────────────
|
||||
round_table: { blocksNavigation: true },
|
||||
table_rect: { blocksNavigation: true },
|
||||
pingpong: { blocksNavigation: true },
|
||||
// ── storage / shelving ────────────────────────────────────────────────────
|
||||
bookshelf: { blocksNavigation: true },
|
||||
cabinet: { blocksNavigation: true },
|
||||
wall_cabinet: { blocksNavigation: false }, // wall-mounted; agents walk under
|
||||
// ── kitchen appliances ────────────────────────────────────────────────────
|
||||
fridge: { blocksNavigation: true },
|
||||
stove: { blocksNavigation: true },
|
||||
microwave: { blocksNavigation: false }, // counter-top / elevated
|
||||
dishwasher: { blocksNavigation: true }, // floor appliance (issue #4)
|
||||
sink: { blocksNavigation: true },
|
||||
coffee_machine: { blocksNavigation: false }, // elevated on counter
|
||||
// ── office equipment ──────────────────────────────────────────────────────
|
||||
printer: { blocksNavigation: true },
|
||||
vending: { blocksNavigation: true },
|
||||
atm: { blocksNavigation: true },
|
||||
whiteboard: { blocksNavigation: true },
|
||||
computer: { blocksNavigation: false }, // desk item
|
||||
keyboard: { blocksNavigation: false }, // desk decoration
|
||||
mouse: { blocksNavigation: false }, // desk decoration
|
||||
// ── server room ───────────────────────────────────────────────────────────
|
||||
server_rack: { blocksNavigation: true },
|
||||
server_terminal: { blocksNavigation: true }, // floor-standing terminal (issue #4)
|
||||
sms_booth: { blocksNavigation: true },
|
||||
phone_booth: { blocksNavigation: true },
|
||||
// ── QA lab ────────────────────────────────────────────────────────────────
|
||||
qa_terminal: { blocksNavigation: true },
|
||||
device_rack: { blocksNavigation: true },
|
||||
test_bench: { blocksNavigation: true },
|
||||
// ── gym ───────────────────────────────────────────────────────────────────
|
||||
treadmill: { blocksNavigation: true },
|
||||
weight_bench: { blocksNavigation: true },
|
||||
dumbbell_rack: { blocksNavigation: true },
|
||||
exercise_bike: { blocksNavigation: true },
|
||||
punching_bag: { blocksNavigation: true },
|
||||
rowing_machine: { blocksNavigation: true },
|
||||
kettlebell_rack: { blocksNavigation: true },
|
||||
yoga_mat: { blocksNavigation: true },
|
||||
// ── art room ──────────────────────────────────────────────────────────────
|
||||
easel: { blocksNavigation: true }, // floor-standing prop (issue #4)
|
||||
// ── water cooler ──────────────────────────────────────────────────────────
|
||||
water_cooler: { blocksNavigation: true }, // freestanding floor appliance (issue #4)
|
||||
// ── decorative / small ────────────────────────────────────────────────────
|
||||
plant: { blocksNavigation: true },
|
||||
lamp: { blocksNavigation: false }, // floor lamp but thin; passable in practice
|
||||
trash: { blocksNavigation: false }, // small bin
|
||||
clock: { blocksNavigation: false }, // wall-mounted
|
||||
mug: { blocksNavigation: false }, // desk item
|
||||
};
|
||||
|
||||
export const FURNITURE_ROTATION: Record<string, number> = {
|
||||
couch: Math.PI,
|
||||
couch_v: Math.PI / 2,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import {
|
||||
getItemBounds,
|
||||
ITEM_FOOTPRINT,
|
||||
ITEM_METADATA,
|
||||
snap,
|
||||
} from "@/features/retro-office/core/geometry";
|
||||
import type {
|
||||
@@ -75,49 +76,20 @@ const GRID_ROWS = Math.ceil(CANVAS_H / GRID_CELL);
|
||||
|
||||
export type NavGrid = Uint8Array;
|
||||
|
||||
// These types define the coarse collision world for pathfinding. Keep this list conservative:
|
||||
// add types here when they materially block walking, and use dedicated route helpers when a
|
||||
// room needs staged entry behavior instead of a single destination point.
|
||||
const BLOCKING_TYPES = new Set([
|
||||
"wall",
|
||||
"round_table",
|
||||
"couch",
|
||||
"couch_v",
|
||||
"executive_desk",
|
||||
"bookshelf",
|
||||
"pingpong",
|
||||
"table_rect",
|
||||
"cabinet",
|
||||
"fridge",
|
||||
"plant",
|
||||
"whiteboard",
|
||||
"vending",
|
||||
"atm",
|
||||
"sms_booth",
|
||||
"phone_booth",
|
||||
"server_rack",
|
||||
"sink",
|
||||
"printer",
|
||||
"stove",
|
||||
"microwave",
|
||||
"qa_terminal",
|
||||
"device_rack",
|
||||
"test_bench",
|
||||
"treadmill",
|
||||
"weight_bench",
|
||||
"dumbbell_rack",
|
||||
"exercise_bike",
|
||||
"punching_bag",
|
||||
"rowing_machine",
|
||||
"kettlebell_rack",
|
||||
"yoga_mat",
|
||||
]);
|
||||
/**
|
||||
* Returns true if the given item type should block pathfinding cells.
|
||||
* Driven by ITEM_METADATA.blocksNavigation — the single source of truth for
|
||||
* nav-blocking behaviour. Unknown types default to false (non-blocking) so
|
||||
* newly added decorative items never accidentally block navigation.
|
||||
*/
|
||||
const itemBlocksNavigation = (type: string): boolean =>
|
||||
ITEM_METADATA[type]?.blocksNavigation ?? false;
|
||||
|
||||
export function buildNavGrid(furniture: FurnitureItem[]): NavGrid {
|
||||
const grid = new Uint8Array(GRID_COLS * GRID_ROWS);
|
||||
const pad = GRID_CELL * 0.6;
|
||||
for (const item of furniture) {
|
||||
if (!BLOCKING_TYPES.has(item.type)) continue;
|
||||
if (!itemBlocksNavigation(item.type)) continue;
|
||||
const bounds = getItemBounds(item);
|
||||
const x1 = bounds.x - pad;
|
||||
const y1 = bounds.y - pad;
|
||||
|
||||
Reference in New Issue
Block a user