mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
test(2.5D): cover rigid group boundaries and fallback
Issue: #651 User-Visible: no
This commit is contained in:
@@ -10337,6 +10337,33 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: ' if (false && boundsIntersect(groupBounds[left], groupBounds[right])) join(left, right);',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'iso-rigid-groups-cross-room-boundary',
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="never join close markers" test/iso-overlays.test.mjs',
|
||||
because: '#651 groups markers only within one owning room. Removing the owner check lets a '
|
||||
+ 'close pair across a shared wall move as one group and cross the room boundary',
|
||||
patches: [{
|
||||
file: 'src/iso-overlays.ts',
|
||||
find: ' if (stable[right].placement.owner?.id !== owner) continue;',
|
||||
replace: ' if (false) continue; // mutant: room ownership no longer separates groups',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'iso-rigid-fallback-drops-room-wall-overlap-priority',
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="rigid fallback prioritizes" test/iso-overlays.test.mjs',
|
||||
because: '#651 degraded placement must preserve room ownership first, clear walls second and '
|
||||
+ 'accept inter-group overlap only last. Distance-only fallback can pin a marker in masonry',
|
||||
patches: [{
|
||||
file: 'src/iso-overlays.ts',
|
||||
find: ' return left.roomViolations - right.roomViolations\n'
|
||||
+ ' || left.wallViolations - right.wallViolations\n'
|
||||
+ ' || left.overlapPenalty - right.overlapPenalty\n'
|
||||
+ ' || rigidOffsetOrder(left.offset, right.offset);',
|
||||
replace: ' return rigidOffsetOrder(left.offset, right.offset);',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'iso-scene-restores-per-marker-collision-resolver',
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
|
||||
@@ -146,6 +146,66 @@ test('rigid overlay groups preserve a row and use one deterministic wall displac
|
||||
}
|
||||
});
|
||||
|
||||
test('rigid overlay groups never join close markers owned by different rooms', () => {
|
||||
const left = square('left', 0, 0, 100, 160, [50, 80]);
|
||||
const right = square('right', 100, 0, 200, 160, [150, 80]);
|
||||
const wall = { outer: [[94, 0], [106, 0], [106, 160], [94, 160]] };
|
||||
const make = (id, room, x) => ({
|
||||
id,
|
||||
kind: 'device',
|
||||
placement: placement({
|
||||
floorAnchor: [x, 80], rooms: [left, right], preferredRoomId: room.id,
|
||||
wallSilhouettes: [wall], footprintHalfSize: [5, 5], wallHeight: 0,
|
||||
visualOffset: 0, sceneUnitsPerCssPixel: 1, camera: identityCamera,
|
||||
}),
|
||||
screenHalfSize: [7, 7],
|
||||
});
|
||||
const leftItem = make('left-device', left, 90);
|
||||
const rightItem = make('right-device', right, 110);
|
||||
const result = resolveIsoOverlayRigidGroups({
|
||||
items: [leftItem, rightItem], rooms: [left, right], wallSilhouettes: [wall],
|
||||
sceneUnitsPerCssPixel: 1, visualOffset: 0, camera: identityCamera,
|
||||
});
|
||||
const leftPlacement = result.placements.get(isoOverlayCollisionKey('device', leftItem.id));
|
||||
const rightPlacement = result.placements.get(isoOverlayCollisionKey('device', rightItem.id));
|
||||
assert.ok(leftPlacement.nudgeCss[0] < 0,
|
||||
'the left-room marker clears the shared wall into its own room');
|
||||
assert.ok(rightPlacement.nudgeCss[0] > 0,
|
||||
'the right-room marker clears the shared wall into its own room');
|
||||
assert.notDeepEqual(leftPlacement.nudgeCss, rightPlacement.nudgeCss,
|
||||
'close markers across a room boundary must remain separate groups');
|
||||
});
|
||||
|
||||
test('rigid fallback prioritizes room, then wall, then overlap inside the 48px cap', () => {
|
||||
const obstacleRoom = square('obstacle-room', 40, 0, 60, 40, [50, 30]);
|
||||
const targetRoom = square('target-room', 40, 0, 60, 40, [50, 30]);
|
||||
const wall = { outer: [[40, 0], [60, 0], [60, 14], [40, 14]] };
|
||||
const make = (id, room, y) => ({
|
||||
id,
|
||||
kind: 'device',
|
||||
placement: placement({
|
||||
floorAnchor: [50, y], rooms: [obstacleRoom, targetRoom], preferredRoomId: room.id,
|
||||
wallSilhouettes: [wall], footprintHalfSize: [5, 5], wallHeight: 0,
|
||||
visualOffset: 0, sceneUnitsPerCssPixel: 1, camera: identityCamera,
|
||||
}),
|
||||
screenHalfSize: [9, 9],
|
||||
});
|
||||
const obstacle = make('a-obstacle', obstacleRoom, 30);
|
||||
const target = make('b-target', targetRoom, 18);
|
||||
const result = resolveIsoOverlayRigidGroups({
|
||||
items: [obstacle, target], rooms: [obstacleRoom, targetRoom], wallSilhouettes: [wall],
|
||||
sceneUnitsPerCssPixel: 1, visualOffset: 0, camera: identityCamera,
|
||||
});
|
||||
const resolved = result.placements.get(isoOverlayCollisionKey('device', target.id));
|
||||
assert.equal(resolved.status, 'degraded', 'the narrow room has no conflict-free placement');
|
||||
assert.equal(resolved.reason, 'overlay-collision',
|
||||
'after preserving room ownership and clearing the wall, overlap is the remaining debt');
|
||||
assert.equal(resolved.nearWallAfter, false,
|
||||
'wall clearance outranks overlap with the previously accepted group');
|
||||
assert.ok(resolved.nudgeCss[1] > 0 && resolved.nudgeDistanceCss <= 48,
|
||||
'the fallback moves into the room without exceeding the global cap');
|
||||
});
|
||||
|
||||
test('group collision checks room-edge events before accepting a legal farther hint', () => {
|
||||
const room = {
|
||||
id: 'diagonal-strip',
|
||||
|
||||
Reference in New Issue
Block a user