208 lines
6.4 KiB
JavaScript
208 lines
6.4 KiB
JavaScript
import { create } from 'zustand';
|
|
|
|
// ── Stardew-inspired Zone definitions ───────────────────────────────
|
|
const ZONES = {
|
|
'pod-a': {
|
|
id: 'pod-a',
|
|
name: 'Workshop',
|
|
type: 'collaboration',
|
|
color: '#A0522D',
|
|
bounds: { row: 2, col: 8, rows: 5, cols: 5 },
|
|
},
|
|
'pod-b': {
|
|
id: 'pod-b',
|
|
name: 'Studio',
|
|
type: 'collaboration',
|
|
color: '#DEB887',
|
|
bounds: { row: 2, col: 14, rows: 5, cols: 5 },
|
|
},
|
|
'solo-1': {
|
|
id: 'solo-1',
|
|
name: 'Cabins',
|
|
type: 'solo',
|
|
color: '#8B4513',
|
|
bounds: { row: 2, col: 0, rows: 10, cols: 6 },
|
|
},
|
|
'solo-2': {
|
|
id: 'solo-2',
|
|
name: 'Library',
|
|
type: 'solo',
|
|
color: '#5B8FA8',
|
|
bounds: { row: 2, col: 22, rows: 10, cols: 6 },
|
|
},
|
|
'coaching': {
|
|
id: 'coaching',
|
|
name: 'Shrine',
|
|
type: 'self-improvement',
|
|
color: '#DAA520',
|
|
bounds: { row: 13, col: 8, rows: 4, cols: 12 },
|
|
},
|
|
'coffee': {
|
|
id: 'coffee',
|
|
name: 'Campfire',
|
|
type: 'social',
|
|
color: '#FF8C42',
|
|
bounds: { row: 0, col: 10, rows: 2, cols: 8 },
|
|
},
|
|
};
|
|
|
|
// ── Agent definitions ──────────────────────────────────────────────
|
|
const AGENT_DEFS = {
|
|
cos: { id: 'cos', name: 'COS', department: 'cos', color: '#FF6B35' },
|
|
engineering: { id: 'engineering', name: 'Engineering', department: 'engineering', color: '#4ECDC4' },
|
|
'senior-eng': { id: 'senior-eng', name: 'Senior Eng', department: 'senior-eng', color: '#7B2D8E' },
|
|
trust: { id: 'trust', name: 'Trust', department: 'trust', color: '#45B7D1' },
|
|
market: { id: 'market', name: 'Market', department: 'market', color: '#96CEB4' },
|
|
health: { id: 'health', name: 'Health', department: 'health', color: '#FFEAA7' },
|
|
content: { id: 'content', name: 'Content', department: 'content', color: '#DDA0DD' },
|
|
presentations: { id: 'presentations', name: 'Presentations', department: 'presentations', color: '#FF8A80' },
|
|
vault: { id: 'vault', name: 'Vault', department: 'vault', color: '#A5D6A7' },
|
|
};
|
|
|
|
// ── Default agent positions (row, col on the pixel grid) ───────────
|
|
const DEFAULT_POSITIONS = {
|
|
cos: { row: 4, col: 2 },
|
|
engineering: { row: 5, col: 10 },
|
|
'senior-eng': { row: 6, col: 10 },
|
|
trust: { row: 8, col: 2 },
|
|
market: { row: 5, col: 16 },
|
|
health: { row: 4, col: 24 },
|
|
content: { row: 6, col: 16 },
|
|
presentations: { row: 7, col: 16 },
|
|
vault: { row: 8, col: 24 },
|
|
};
|
|
|
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
function generateTileMap(gridRows, gridCols) {
|
|
const tiles = [];
|
|
for (let row = 0; row < gridRows; row++) {
|
|
tiles[row] = [];
|
|
for (let col = 0; col < gridCols; col++) {
|
|
tiles[row][col] = { type: 'floor', zoneId: findZoneForTile(row, col) };
|
|
}
|
|
}
|
|
return { rows: gridRows, cols: gridCols, tiles };
|
|
}
|
|
|
|
function findZoneForTile(row, col) {
|
|
for (const zone of Object.values(ZONES)) {
|
|
const { row: zr, col: zc, rows, cols } = zone.bounds;
|
|
if (row >= zr && row < zr + rows && col >= zc && col < zc + cols) {
|
|
return zone.id;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ── Store ──────────────────────────────────────────────────────────
|
|
const useStore = create((set, get) => ({
|
|
// Connection state
|
|
wsConnected: false,
|
|
wsReconnecting: false,
|
|
|
|
// World state
|
|
grid: generateTileMap(20, 28),
|
|
zones: ZONES,
|
|
agents: {},
|
|
|
|
// Initialize from full state snapshot
|
|
initFromSnapshot(snapshot) {
|
|
const agents = {};
|
|
for (const agentId of Object.keys(AGENT_DEFS)) {
|
|
const def = AGENT_DEFS[agentId];
|
|
const pos = DEFAULT_POSITIONS[agentId];
|
|
agents[agentId] = {
|
|
...def,
|
|
position: { row: pos.row, col: pos.col },
|
|
targetPosition: { row: pos.row, col: pos.col },
|
|
activity: 'idle',
|
|
currentTask: null,
|
|
lastHeartbeat: Date.now(),
|
|
pulse: 0,
|
|
};
|
|
}
|
|
// Apply snapshot overrides if provided
|
|
if (snapshot && snapshot.agents) {
|
|
for (const [id, data] of Object.entries(snapshot.agents)) {
|
|
if (agents[id]) {
|
|
Object.assign(agents[id], data);
|
|
if (data.position) {
|
|
agents[id].targetPosition = { ...data.position };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
set({ agents, wsConnected: true, wsReconnecting: false });
|
|
},
|
|
|
|
// Apply a diff update
|
|
applyDiff(diffs) {
|
|
const state = get();
|
|
const updated = { ...state.agents };
|
|
|
|
for (const diff of diffs) {
|
|
if (diff.op === 'agent.move' || diff.op === 'agent.update') {
|
|
const agentId = diff.path.split('.')[1];
|
|
if (updated[agentId]) {
|
|
const delta = diff.value || {};
|
|
if (delta.position) {
|
|
delta.targetPosition = { ...delta.position };
|
|
}
|
|
Object.assign(updated[agentId], delta);
|
|
}
|
|
} else if (diff.op === 'agent.position') {
|
|
const agentId = diff.path.split('.')[1];
|
|
if (updated[agentId]) {
|
|
updated[agentId].targetPosition = { ...diff.value };
|
|
}
|
|
} else if (diff.op === 'agent.activity') {
|
|
const agentId = diff.path.split('.')[1];
|
|
if (updated[agentId]) {
|
|
updated[agentId] = { ...updated[agentId], ...diff.value };
|
|
}
|
|
}
|
|
}
|
|
|
|
set({ agents: updated });
|
|
},
|
|
|
|
// WS connection status
|
|
setWsConnected(connected) {
|
|
set({ wsConnected: connected, wsReconnecting: !connected });
|
|
},
|
|
|
|
setWsReconnecting(reconnecting) {
|
|
set({ wsReconnecting: reconnecting });
|
|
},
|
|
|
|
// Update agent positions directly (used by mock mode)
|
|
updateAgentPosition(agentId, position) {
|
|
set(state => {
|
|
const agents = { ...state.agents };
|
|
if (agents[agentId]) {
|
|
agents[agentId] = {
|
|
...agents[agentId],
|
|
targetPosition: { ...position },
|
|
};
|
|
}
|
|
return { agents };
|
|
});
|
|
},
|
|
|
|
updateAgentActivity(agentId, activity, task) {
|
|
set(state => {
|
|
const agents = { ...state.agents };
|
|
if (agents[agentId]) {
|
|
agents[agentId] = {
|
|
...agents[agentId],
|
|
activity,
|
|
currentTask: task || agents[agentId].currentTask,
|
|
};
|
|
}
|
|
return { agents };
|
|
});
|
|
},
|
|
}));
|
|
|
|
export { ZONES, AGENT_DEFS, DEFAULT_POSITIONS };
|
|
export default useStore;
|