Office life: everyone roams, meetings convene in pairs, card follows agent
- The detail card now rides beside the clicked agent (flipping and clamping at viewport edges via a pure cardPositionFor helper) instead of pinning to the top-right corner; it follows a walking agent - Every agent - the CEO and busy agents included - periodically visits the coffee machine, snack bar, and restroom; work status no longer recalls a walker mid-break - The office convenes meetings around the meeting table every few minutes: at least two settled attendees walk over, dwell together on distinct table spots, and head back; a solo office never meets - Meeting spots ship as lane-wired amenities so pathing stays correct by construction; floor-plan invariants (collider-free, reachable) cover them automatically - e2e asserts the card opens beside the sprite, not in a corner
This commit is contained in:
1 parent
e55b8b85b0
commit
c3924af9ac
10 files changed
+206
-36
No files matched your search
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { cardPositionFor, CARD_W } from "../../src/renderer/src/pages/office/agent-card-position";
|
||||
|
||||
describe("agent detail card placement", () => {
|
||||
test("sits to the right of the sprite, vertically centered on it", () => {
|
||||
const position = cardPositionFor({ x: 400, y: 300 }, { x: 0, y: 0 }, 1200, 800, 170);
|
||||
expect(position.left).toBe(418);
|
||||
expect(position.top).toBe(300 - 16 - 85);
|
||||
});
|
||||
|
||||
test("flips to the left when the right edge would leave the viewport", () => {
|
||||
const position = cardPositionFor({ x: 1100, y: 300 }, { x: 0, y: 0 }, 1200, 800, 170);
|
||||
expect(position.left).toBe(1100 - CARD_W - 18);
|
||||
expect(position.left + CARD_W).toBeLessThanOrEqual(1200 - 8);
|
||||
});
|
||||
|
||||
test("clamps fully on screen near every edge", () => {
|
||||
for (const agent of [{ x: 5, y: 5 }, { x: 5, y: 795 }, { x: 1195, y: 5 }, { x: 1195, y: 795 }]) {
|
||||
const position = cardPositionFor(agent, { x: 0, y: 0 }, 1200, 800, 170);
|
||||
expect(position.left).toBeGreaterThanOrEqual(8);
|
||||
expect(position.left + CARD_W).toBeLessThanOrEqual(1192);
|
||||
expect(position.top).toBeGreaterThanOrEqual(8);
|
||||
expect(position.top + 170).toBeLessThanOrEqual(792);
|
||||
}
|
||||
});
|
||||
|
||||
test("accounts for the camera offset", () => {
|
||||
const onScreen = cardPositionFor({ x: 700, y: 500 }, { x: 300, y: 200 }, 1200, 800, 170);
|
||||
const noCamera = cardPositionFor({ x: 400, y: 300 }, { x: 0, y: 0 }, 1200, 800, 170);
|
||||
expect(onScreen).toEqual(noCamera);
|
||||
});
|
||||
});
|
||||
@@ -30,6 +30,8 @@ describe("office floor plan", () => {
|
||||
expect(plan.amenities.filter((a) => a.kind === "restroom").length).toBeGreaterThanOrEqual(2);
|
||||
expect(plan.amenities.some((a) => a.kind === "coffee")).toBe(true);
|
||||
expect(plan.amenities.some((a) => a.kind === "snack")).toBe(true);
|
||||
// Meetings need at least two attendees, so the table has spots for them.
|
||||
expect(plan.amenities.filter((a) => a.kind === "meeting").length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
test("desk field grows by rows so no head-count is ever cut off", () => {
|
||||
|
||||
@@ -121,30 +121,72 @@ describe("office simulation", () => {
|
||||
expect(agent.carrying).toBe("coffee");
|
||||
});
|
||||
|
||||
test("working agents never leave their desks", () => {
|
||||
test("working agents (and the CEO) take breaks too", () => {
|
||||
const sim = new OfficeSimulation(() => 0);
|
||||
sim.sync([agentInput("a", "staff", "active")]);
|
||||
sim.sync([agentInput("ceo", "c_suite", "active")]);
|
||||
const agent = sim.agents[0];
|
||||
for (let i = 0; i < 600; i++) sim.step(16, IDLE);
|
||||
expect(agent.behavior.kind).toBe("seated");
|
||||
expect(agent.x).toBe(agent.seat.x);
|
||||
expect(agent.y).toBe(agent.seat.y);
|
||||
let leftTheDesk = false;
|
||||
for (let i = 0; i < 3000 && !leftTheDesk; i++) {
|
||||
sim.step(16, IDLE);
|
||||
if (agent.behavior.kind !== "seated") leftTheDesk = true;
|
||||
}
|
||||
expect(leftTheDesk).toBe(true);
|
||||
});
|
||||
|
||||
test("an agent called to work abandons the break and returns to their desk", () => {
|
||||
test("a snapshot refresh mid-break never recalls the walker", () => {
|
||||
const sim = new OfficeSimulation(() => 0);
|
||||
sim.sync([agentInput("a")]);
|
||||
const agent = sim.agents[0];
|
||||
for (let i = 0; i < 3000 && agent.behavior.kind === "seated"; i++) sim.step(16, IDLE);
|
||||
for (let i = 0; i < 30; i++) sim.step(16, IDLE);
|
||||
expect(agent.behavior.kind).toBe("walking");
|
||||
expect(agent.x === agent.seat.x && agent.y === agent.seat.y).toBe(false);
|
||||
|
||||
// Becoming busy does not cancel the human ritual of a coffee run.
|
||||
sim.sync([agentInput("a", "staff", "active")]);
|
||||
for (let i = 0; i < 6000 && sim.agents[0].behavior.kind !== "seated"; i++) sim.step(16, IDLE);
|
||||
const refreshed = sim.agents[0];
|
||||
expect(refreshed.behavior.kind).toBe("walking");
|
||||
let dwelled = false;
|
||||
for (let i = 0; i < 6000 && !dwelled; i++) {
|
||||
sim.step(16, IDLE);
|
||||
if (refreshed.behavior.kind === "dwelling") dwelled = true;
|
||||
}
|
||||
expect(dwelled).toBe(true);
|
||||
});
|
||||
|
||||
test("meetings convene at least two attendees around the table, then everyone returns", () => {
|
||||
const sim = new OfficeSimulation(() => 0);
|
||||
sim.sync([agentInput("a", "staff", "active"), agentInput("b"), agentInput("ceo", "c_suite", "active")]);
|
||||
// Nobody has settled yet: a meeting cannot be convened.
|
||||
expect(sim.conveneMeeting()).toBe(0);
|
||||
for (const agent of sim.agents) agent.settledMs = 30_000;
|
||||
|
||||
const attendees = sim.conveneMeeting();
|
||||
expect(attendees).toBeGreaterThanOrEqual(2);
|
||||
const walkers = sim.agents.filter((agent) => agent.behavior.kind === "walking");
|
||||
expect(walkers).toHaveLength(attendees);
|
||||
const spots = new Set(
|
||||
walkers.map((agent) => (agent.behavior.kind === "walking" ? `${agent.behavior.spot?.stand.x}` : "")),
|
||||
);
|
||||
expect(spots.size).toBe(attendees);
|
||||
|
||||
// Everyone reaches the meeting room, dwells together, and heads home.
|
||||
let overlapped = false;
|
||||
for (let i = 0; i < 12000 && !overlapped; i++) {
|
||||
sim.step(16, IDLE);
|
||||
const dwellingInMeeting = sim.agents.filter(
|
||||
(agent) => agent.behavior.kind === "dwelling" && agent.behavior.spot.kind === "meeting",
|
||||
);
|
||||
if (dwellingInMeeting.length >= 2) overlapped = true;
|
||||
}
|
||||
expect(overlapped).toBe(true);
|
||||
});
|
||||
|
||||
test("a solo office never convenes a meeting", () => {
|
||||
const sim = new OfficeSimulation(() => 0);
|
||||
sim.sync([agentInput("a")]);
|
||||
sim.agents[0].settledMs = 30_000;
|
||||
expect(sim.conveneMeeting()).toBe(0);
|
||||
expect(sim.agents[0].behavior.kind).toBe("seated");
|
||||
expect(sim.agents[0].x).toBe(sim.agents[0].seat.x);
|
||||
expect(sim.agents[0].y).toBe(sim.agents[0].seat.y);
|
||||
});
|
||||
|
||||
test("clicks hit the agent drawn at that spot, frontmost first", () => {
|
||||
|
||||
Reference in new issue
Block a user