feat(frontend): tile-based office interior with LimeZu Modern Interiors tileset

- Replace outdoor farm scene with 20x15 tile office floor plan
- Add officeMap.js with tile definitions, map grid, walls, doorways, furniture
- Rewrite FarmCanvas.jsx with layered tile rendering (background, floor, walls, doors, furniture, agents)
- Use tileset.png for floor/wall/furniture tiles with proper source coordinates
- Design 6-room layout: Briefing Room, Commons, War Room, Engineering Bay, Quiet Zone, Ops Center
- Central hallway connecting all rooms with doorway openings
- Room labels drawn as floating pills
- Default agent positions reassigned to appropriate rooms
- Remove old BeOS icon tiles (replaced by LimeZu tileset)
This commit is contained in:
engineering Agent 2026-05-19 19:42:31 -07:00
parent 399c7958ff
commit a72920a771
850 changed files with 663 additions and 324 deletions

1
assets/dawnlike Submodule

@ -0,0 +1 @@
Subproject commit b5898187c2253d9213e4ae02d7228e81cdace302

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1,11 @@
FREE VERSION LICENSE:
CAN:
YOU CAN USE THE ASSET IN NON COMMERCIAL PROJECTS
YOU CAN EDIT THE SPRITES AND USE THEM IN NON COMMERCIAL PROJECTS
CAN'T:
YOU CAN'T USE THE ASSET IN COMMERCIAL PROJECTS
YOU CAN'T EDIT THE SPRITES AND USE THEM IN COMMERCIAL PROJECTS
YOU CAN'T EDIT AND RESELL THE SPRITES

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -0,0 +1,5 @@
Hi, thank you for downloading the free version!
This version has around 1% of material of the full asset
Consider buying the complete version (1.20$) if you like the asset :)

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View file

@ -1,23 +1,12 @@
import React, { useRef, useEffect, useState, useCallback } from 'react';
import useStore, { ZONES } from './store.js';
import useStore from './store.js';
import { OFFICE_MAP, WALLS, DOORWAYS, FURNITURE, TILE_SIZE, MAP_COLS, MAP_ROWS } from './officeMap.js';
const TILE_SIZE = 32;
const COLS = 28;
const ROWS = 20;
const CANVAS_W = COLS * TILE_SIZE; // 896
const CANVAS_H = ROWS * TILE_SIZE; // 640
const CANVAS_W = MAP_COLS * TILE_SIZE; // 640
const CANVAS_H = MAP_ROWS * TILE_SIZE; // 480
const LERP_SPEED = 0.15;
// ── Building definitions (grid coords) ─────────────────────────
const BUILDINGS = [
{ id: 'workshop', zoneId: 'pod-a', label: 'Workshop', color: '#A0522D', col: 8, row: 2, w: 5, h: 5, roofColor: '#6B4226' },
{ id: 'studio', zoneId: 'pod-b', label: 'Studio', color: '#DEB887', col: 14, row: 2, w: 5, h: 5, roofColor: '#8B6914' },
{ id: 'cabins', zoneId: 'solo-1', label: 'Cabins', color: '#8B4513', col: 0, row: 2, w: 6, h: 10, roofColor: '#6B4226' },
{ id: 'library', zoneId: 'solo-2', label: 'Library', color: '#5B8FA8', col: 22, row: 2, w: 6, h: 10, roofColor: '#2F5F7A' },
{ id: 'shrine', zoneId: 'coaching', label: 'Shrine', color: '#DAA520', col: 8, row: 13, w: 12, h: 3, roofColor: '#C68B3C' },
];
// ── Sprite loader ────────────────────────────────────────────────
function loadSprite(id) {
const img = new Image();
@ -31,6 +20,25 @@ const AGENT_IDS = ['cos', 'engineering', 'senior-eng', 'trust', 'market', 'healt
const sprites = {};
AGENT_IDS.forEach(id => { sprites[id] = loadSprite(id); });
// ── Tileset loader ────────────────────────────────────────────────
let tilesetImg = null;
function loadTileset() {
if (!tilesetImg) {
tilesetImg = new Image();
tilesetImg.src = '/office/tiles/tileset.png';
}
return tilesetImg;
}
let roomBuilderImg = null;
function loadRoomBuilder() {
if (!roomBuilderImg) {
roomBuilderImg = new Image();
roomBuilderImg.src = '/office/tiles/room-builder.png';
}
return roomBuilderImg;
}
// ── Particle system for sparkles ─────────────────────────────────
let sparkles = [];
@ -46,301 +54,189 @@ function spawnSparkle(x, y) {
if (sparkles.length > 60) sparkles.shift();
}
// ── BeOS activity icons ──────────────────────────────────────────
const activityIcons = {};
const ACTIVITY_ICONS = [
'dispatch', 'code', 'build', 'review', 'research', 'analyze',
'write', 'security', 'monitor', 'memory', 'coach', 'heartbeat',
'mail', 'collaborate', 'present', 'vault', 'think', 'idle',
'blocked', 'document', 'terminal', 'package', 'network',
'search', 'design', 'data',
];
// ── Tile drawing helpers ──────────────────────────────────────────
function loadActivityIcons() {
ACTIVITY_ICONS.forEach(name => {
const img = new Image();
img.src = `/office/beos-icons/${name}.png`;
activityIcons[name] = img;
});
// Draw a single tile from tileset.png (16-wide grid)
function drawTileFromTileset(ctx, tileId, dx, dy) {
if (tileId < 0 || tileId === undefined) return;
const img = loadTileset();
if (!img || !img.complete) return;
const sx = (tileId % 16) * TILE_SIZE;
const sy = Math.floor(tileId / 16) * TILE_SIZE;
ctx.drawImage(img, sx, sy, TILE_SIZE, TILE_SIZE, dx, dy, TILE_SIZE, TILE_SIZE);
}
// Map agent state → BeOS icon name
function getActivityIcon(agent) {
const activity = agent.activity || 'idle';
if (activity === 'idle' || activity === 'offline') return 'idle';
if (activity === 'blocked') return 'blocked';
if (activity === 'thinking') return 'think';
// activity === 'working' → check currentTask keywords
const task = (agent.currentTask || '').toLowerCase();
if (/\b(dispatch|rout)\b/.test(task)) return 'dispatch';
if (/\b(code|implement|build|pr)\b/.test(task)) return /\bbuild\b/.test(task) ? 'build' : 'code';
if (/\b(review|audit)\b/.test(task)) return 'review';
if (/\bresearch\b/.test(task)) return 'research';
if (/\bsearch\b/.test(task)) return 'search';
if (/\banalyz\b/.test(task)) return 'analyze';
if (/\b(writ|draft|content|blog)\b/.test(task)) return 'write';
if (/\bsecurit|complianc\b/.test(task)) return 'security';
if (/\bmonitor\b/.test(task)) return 'monitor';
if (/\bhealth|check\b/.test(task)) return 'heartbeat';
if (/\b(memory|index|vault|note)\b/.test(task)) return /\bvault\b/.test(task) ? 'vault' : 'memory';
if (/\b(coach|learn|improv)\b/.test(task)) return 'coach';
if (/\b(mail|message|communicat)\b/.test(task)) return 'mail';
if (/\b(collaborat|pair|meeting)\b/.test(task)) return 'collaborate';
if (/\b(present|slide|deck)\b/.test(task)) return 'present';
if (/\b(document|spec|readme)\b/.test(task)) return 'document';
if (/\b(terminal|cli|script)\b/.test(task)) return 'terminal';
if (/\b(deploy|package|release)\b/.test(task)) return 'package';
if (/\b(network|connect|api)\b/.test(task)) return 'network';
if (/\b(design|layout|ui|css)\b/.test(task)) return 'design';
if (/\b(data|stats)\b/.test(task)) return 'data';
// Default: code icon for working agents
return 'code';
// Draw a single tile from room-builder.png (17-wide grid)
function drawTileFromRoomBuilder(ctx, tileId, dx, dy) {
if (tileId < 0 || tileId === undefined) return;
const img = loadRoomBuilder();
if (!img || !img.complete) return;
const RB_COLS = 17;
const sx = (tileId % RB_COLS) * TILE_SIZE;
const sy = Math.floor(tileId / RB_COLS) * TILE_SIZE;
ctx.drawImage(img, sx, sy, TILE_SIZE, TILE_SIZE, dx, dy, TILE_SIZE, TILE_SIZE);
}
// ── Draw helpers ──────────────────────────────────────────────────
function drawGrass(ctx) {
// Dark green base
ctx.fillStyle = '#2D5A27';
// ── Layer 1: Background / outdoors ────────────────────────────────
function drawBackground(ctx) {
// Dark exterior (outside the office)
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
// Random pixel texture dots
const imageData = ctx.getImageData(0, 0, CANVAS_W, CANVAS_H);
for (let i = 0; i < 2000; i++) {
const x = Math.floor(Math.random() * CANVAS_W);
const y = Math.floor(Math.random() * CANVAS_H);
const idx = (y * CANVAS_W + x) * 4;
const shade = 60 + Math.random() * 40;
imageData.data[idx] = shade + 20;
imageData.data[idx + 1] = shade + 40;
imageData.data[idx + 2] = shade;
imageData.data[idx + 3] = 255;
// Grass/texture around building (top strip)
ctx.fillStyle = '#2D5A27';
ctx.fillRect(0, 0, CANVAS_W, 3 * TILE_SIZE);
// Add some texture to the grass
for (let i = 0; i < 300; i++) {
const x = Math.random() * CANVAS_W;
const y = Math.random() * 3 * TILE_SIZE;
ctx.fillStyle = `rgb(${40 + Math.random() * 30},${70 + Math.random() * 40},${30 + Math.random() * 20})`;
ctx.fillRect(x, y, 2, 2);
}
ctx.putImageData(imageData, 0, 0);
}
function drawDirtPaths(ctx) {
ctx.fillStyle = '#8B6914';
// Horizontal paths
ctx.fillRect(0, 48, CANVAS_W, 16);
ctx.fillRect(0, 224, CANVAS_W, 16);
ctx.fillRect(0, 544, CANVAS_W, 16);
// Vertical paths
ctx.fillRect(208, 0, 16, CANVAS_H);
ctx.fillRect(432, 0, 16, CANVAS_H);
ctx.fillRect(704, 0, 16, CANVAS_H);
// Path edge shading
ctx.fillStyle = 'rgba(0,0,0,0.15)';
ctx.fillRect(0, 48, CANVAS_W, 2);
ctx.fillRect(0, 224, CANVAS_W, 2);
ctx.fillRect(0, 544, CANVAS_W, 2);
}
function drawBuilding(ctx, b) {
const px = b.col * TILE_SIZE;
const py = b.row * TILE_SIZE;
const pw = b.w * TILE_SIZE;
const ph = b.h * TILE_SIZE;
const roofH = 24;
// Shadow under building
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.fillRect(px + 4, py + 4, pw, ph);
// Roof (triangle)
ctx.fillStyle = b.roofColor;
ctx.beginPath();
ctx.moveTo(px - 2, py);
ctx.lineTo(px + pw / 2, py - roofH);
ctx.lineTo(px + pw + 2, py);
ctx.closePath();
ctx.fill();
// Building body
ctx.fillStyle = b.color;
ctx.fillRect(px, py, pw, ph);
// Brick/wall texture pattern
ctx.strokeStyle = 'rgba(0,0,0,0.2)';
ctx.lineWidth = 1;
for (let r = 0; r < b.h; r++) {
const y = py + r * TILE_SIZE;
ctx.beginPath();
ctx.moveTo(px, y);
ctx.lineTo(px + pw, y);
ctx.stroke();
}
for (let c = 0; c < b.w; c++) {
const x = px + c * TILE_SIZE;
ctx.beginPath();
ctx.moveTo(x, py);
ctx.lineTo(x, py + ph);
ctx.stroke();
// ── Layer 2: Floor tiles ──────────────────────────────────────────
function drawFloorLayer(ctx) {
const img = loadTileset();
if (!img || !img.complete) {
// Fallback: draw solid colors
const colors = {
16: '#C4A882', 22: '#8BA87E', 83: '#9B9B9B', 67: '#B8956E',
38: '#4A5070', 54: '#7A7A85', 70: '#A89B8A',
};
for (let row = 0; row < MAP_ROWS; row++) {
for (let col = 0; col < MAP_COLS; col++) {
const tileId = OFFICE_MAP[row]?.[col];
if (tileId && tileId > 0) {
ctx.fillStyle = colors[tileId] || '#555';
ctx.fillRect(col * TILE_SIZE, row * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
}
}
return;
}
// Door
const doorW = 16;
const doorH = 24;
ctx.fillStyle = '#4A3520';
ctx.fillRect(px + pw / 2 - doorW / 2, py + ph - doorH - 4, doorW, doorH);
ctx.fillStyle = '#DAA520';
ctx.fillRect(px + pw / 2 + doorW / 2 - 4, py + ph - doorH / 2 - 2, 3, 3);
// Windows (small squares)
ctx.fillStyle = '#87CEEB';
if (b.w >= 4) {
for (let side = 0; side < 2; side++) {
const wx = side === 0 ? px + TILE_SIZE * 0.5 : px + pw - TILE_SIZE * 1.5;
ctx.fillRect(wx, py + TILE_SIZE * 1.5, 14, 12);
ctx.strokeStyle = '#6B4226';
ctx.lineWidth = 1;
ctx.strokeRect(wx, py + TILE_SIZE * 1.5, 14, 12);
for (let row = 0; row < MAP_ROWS; row++) {
for (let col = 0; col < MAP_COLS; col++) {
const tileId = OFFICE_MAP[row]?.[col];
if (tileId && tileId > 0) {
drawTileFromTileset(ctx, tileId, col * TILE_SIZE, row * TILE_SIZE);
}
}
}
// Label pill
const labelW = ctx.measureText(b.label).width + 12;
const labelH = 16;
const labelX = px + pw / 2 - labelW / 2;
const labelY = py - roofH - labelH - 2;
ctx.fillStyle = 'rgba(0,0,0,0.65)';
ctx.beginPath();
ctx.roundRect(labelX, labelY, labelW, labelH, 4);
ctx.fill();
ctx.font = 'bold 10px "Courier New", monospace';
ctx.fillStyle = '#FFD700';
ctx.fillText(b.label, labelX + 6, labelY + 12);
}
function drawCampfire(ctx, time) {
const cx = 10 * TILE_SIZE + 4 * TILE_SIZE; // center of campfire (col 10, 8 wide)
const cy = 0 * TILE_SIZE + 32; // row 0, 2 tall
// ── Layer 3: Walls ────────────────────────────────────────────────
function drawWallLayer(ctx) {
const img = loadTileset();
const drawn = new Set();
// Ground circle
ctx.fillStyle = '#E8D5B7';
ctx.beginPath();
ctx.ellipse(cx, cy + 16, 112, 24, 0, 0, Math.PI * 2);
ctx.fill();
for (const wall of WALLS) {
const key = `${wall.row},${wall.col}`;
if (drawn.has(key)) continue;
drawn.add(key);
// Logs
ctx.fillStyle = '#6B4226';
ctx.fillRect(cx - 48, cy + 20, 40, 8);
ctx.fillRect(cx + 8, cy + 20, 40, 8);
ctx.fillRect(cx - 20, cy + 16, 8, 40);
const dx = wall.col * TILE_SIZE;
const dy = wall.row * TILE_SIZE;
// Animated flame
const flicker = Math.sin(time * 0.05) * 4;
const f1 = Math.sin(time * 0.08 + 0.5) * 6;
const f2 = Math.sin(time * 0.11 + 1.2) * 5;
ctx.fillStyle = '#FF8C42';
ctx.beginPath();
ctx.moveTo(cx - 6 + f2, cy + 8);
ctx.quadraticCurveTo(cx - 4 + f1, cy - flicker - 8, cx, cy - flicker - 20);
ctx.quadraticCurveTo(cx + 4 + f1, cy - flicker - 8, cx + 6 + f2, cy + 8);
ctx.fill();
// Inner flame
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.moveTo(cx - 3, cy + 8);
ctx.quadraticCurveTo(cx - 2, cy - 8, cx, cy - 16);
ctx.quadraticCurveTo(cx + 2, cy - 8, cx + 3, cy + 8);
ctx.fill();
// Core
ctx.fillStyle = '#FFFFFF';
ctx.beginPath();
ctx.moveTo(cx - 1, cy + 8);
ctx.quadraticCurveTo(cx, cy - 4, cx, cy - 10);
ctx.quadraticCurveTo(cx + 1, cy - 4, cx + 1, cy + 8);
ctx.fill();
// Warm glow
const glow = ctx.createRadialGradient(cx, cy - 10, 2, cx, cy, 80);
glow.addColorStop(0, 'rgba(255,200,50,0.3)');
glow.addColorStop(0.5, 'rgba(255,140,40,0.1)');
glow.addColorStop(1, 'rgba(255,100,30,0)');
ctx.fillStyle = glow;
ctx.fillRect(cx - 80, cy - 40, 160, 100);
// Label
ctx.fillStyle = 'rgba(0,0,0,0.65)';
ctx.beginPath();
ctx.roundRect(cx - 35, cy - 42, 70, 16, 4);
ctx.fill();
ctx.font = 'bold 10px "Courier New", monospace';
ctx.fillStyle = '#FFD700';
ctx.fillText('Campfire', cx - 30, cy - 30);
if (img && img.complete) {
drawTileFromTileset(ctx, wall.tile, dx, dy);
} else {
// Fallback wall rendering
ctx.fillStyle = '#6B5B4F';
ctx.fillRect(dx, dy, TILE_SIZE, TILE_SIZE);
ctx.strokeStyle = '#5A4A3E';
ctx.lineWidth = 1;
ctx.strokeRect(dx + 1, dy + 1, TILE_SIZE - 2, TILE_SIZE - 2);
}
}
}
function drawDecorations(ctx) {
// Trees
const treePositions = [
...Array.from({ length: 7 }, (_, i) => ({ x: i * 20 + 10, y: 596 })),
...Array.from({ length: 7 }, (_, i) => ({ x: i * 20 + 10, y: 40 })),
{ x: 845, y: 80 }, { x: 845, y: 128 }, { x: 845, y: 176 },
{ x: 10, y: 80 }, { x: 10, y: 128 },
// ── Layer 3.5: Doorways ───────────────────────────────────────────
function drawDoorways(ctx) {
const img = loadTileset();
for (const door of DOORWAYS) {
const dx = door.col * TILE_SIZE;
const dy = door.row * TILE_SIZE;
if (img && img.complete) {
drawTileFromTileset(ctx, door.tile, dx, dy);
} else {
// Fallback doorway
ctx.fillStyle = '#4A3520';
ctx.fillRect(dx, dy, TILE_SIZE, TILE_SIZE);
ctx.fillStyle = '#DAA520';
ctx.fillRect(dx + TILE_SIZE - 5, dy + TILE_SIZE / 2 - 1, 4, 4);
}
}
}
// ── Layer 4: Furniture ────────────────────────────────────────────
function drawFurniture(ctx) {
const img = loadTileset();
for (const furn of FURNITURE) {
const dx = furn.col * TILE_SIZE;
const dy = furn.row * TILE_SIZE;
if (img && img.complete) {
drawTileFromTileset(ctx, furn.tile, dx, dy);
} else {
// Fallback colored furniture
const fColors = {
528: '#8B6914', 936: '#4A3728', 736: '#6B4226', 584: '#2D8B2D',
674: '#DAA520', 1012: '#555', 672: '#8B7355', 996: '#222',
1010: '#444', 889: '#FFD700', 709: '#EEE',
};
ctx.fillStyle = fColors[furn.tile] || '#666';
ctx.fillRect(dx + 2, dy + 2, TILE_SIZE - 4, TILE_SIZE - 4);
}
}
}
// ── Room labels ───────────────────────────────────────────────────
function drawRoomLabels(ctx) {
const rooms = [
{ label: 'Briefing Room', col: 0, row: 3, w: 6 },
{ label: 'Commons', col: 6, row: 3, w: 8 },
{ label: 'War Room', col: 14, row: 3, w: 6 },
{ label: 'Engineering Bay', col: 0, row: 9, w: 6 },
{ label: 'Quiet Zone', col: 6, row: 9, w: 8 },
{ label: 'Ops Center', col: 14, row: 9, w: 6 },
];
for (const t of treePositions) {
// Trunk
ctx.fillStyle = '#6B4226';
ctx.fillRect(t.x - 1, t.y + 8, 3, 8);
// Canopy
ctx.fillStyle = '#2D5A27';
for (const room of rooms) {
const cx = room.col * TILE_SIZE + (room.w * TILE_SIZE) / 2;
const topY = room.row * TILE_SIZE;
const labelW = ctx.measureText(room.label).width + 10;
const labelH = 12;
// Semi-transparent pill behind text
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.beginPath();
ctx.moveTo(t.x, t.y - 8);
ctx.lineTo(t.x - 6, t.y + 8);
ctx.lineTo(t.x + 6, t.y + 8);
ctx.closePath();
ctx.roundRect(cx - labelW / 2, topY + 2, labelW, labelH, 3);
ctx.fill();
ctx.fillStyle = '#4A7C3F';
ctx.beginPath();
ctx.moveTo(t.x, t.y - 4);
ctx.lineTo(t.x - 4, t.y + 4);
ctx.lineTo(t.x + 4, t.y + 4);
ctx.closePath();
ctx.fill();
}
// Rocks
const rocks = [
{ x: 250, y: 560 }, { x: 480, y: 570 }, { x: 420, y: 550 }, { x: 720, y: 560 },
{ x: 380, y: 540 }, { x: 160, y: 570 },
];
for (const r of rocks) {
ctx.fillStyle = '#777';
ctx.fillRect(r.x, r.y + 2, 8, 5);
ctx.fillStyle = '#999';
ctx.fillRect(r.x + 1, r.y, 7, 5);
ctx.font = 'bold 8px "Courier New", monospace';
ctx.textAlign = 'center';
ctx.fillStyle = '#DDD';
ctx.fillText(room.label, cx, topY + 11);
}
ctx.textAlign = 'start';
// Flowers
const flowers = [
{ x: 240, y: 580, c: '#FF6347' }, { x: 500, y: 590, c: '#FFD700' },
{ x: 350, y: 585, c: '#87CEEB' }, { x: 650, y: 595, c: '#DDA0DD' },
{ x: 130, y: 582, c: '#FF6347' }, { x: 580, y: 580, c: '#FFD700' },
];
for (const f of flowers) {
ctx.fillStyle = '#4CAF50';
ctx.fillRect(f.x + 1, f.y + 3, 1, 5);
ctx.fillStyle = f.c;
ctx.fillRect(f.x, f.y, 4, 4);
}
// Fence posts along top path
for (let i = 0; i < 12; i++) {
const fx = 120 + i * 64;
ctx.fillStyle = '#8B4513';
ctx.fillRect(fx, 56, 3, 14);
}
// Hallway label
const hx = MAP_COLS * TILE_SIZE / 2;
ctx.fillStyle = 'rgba(0,0,0,0.4)';
const hlW = ctx.measureText('Hallway').width + 10;
ctx.beginPath();
ctx.roundRect(hx - hlW / 2, 7 * TILE_SIZE + 6, hlW, 12, 3);
ctx.fill();
ctx.font = 'bold 8px "Courier New", monospace';
ctx.textAlign = 'center';
ctx.fillStyle = '#CCC';
ctx.fillText('Hallway', hx, 7 * TILE_SIZE + 15);
ctx.textAlign = 'start';
}
// ── Layer 5: Agents ───────────────────────────────────────────────
function drawAgent(ctx, agent, time) {
if (!agent || !agent.position) return;
@ -371,14 +267,13 @@ function drawAgent(ctx, agent, time) {
}
ctx.save();
ctx.globalAlpha = 0.95;
ctx.translate(px, py + yOffset);
// Activity states
if (activity === 'offline') {
ctx.globalAlpha = 0.5;
ctx.filter = 'grayscale(100%)';
} else if (activity === 'blocked') {
ctx.globalAlpha = 1;
}
// Draw sprite centered
@ -389,39 +284,28 @@ function drawAgent(ctx, agent, time) {
ctx.restore();
// ── Activity icon above agent ──────────────────────────────
const iconName = getActivityIcon(agent);
const iconImg = activityIcons[iconName];
if (iconImg && iconImg.complete && iconImg.naturalWidth > 0) {
ctx.save();
ctx.globalAlpha = 1;
ctx.drawImage(iconImg, px - 12, py + yOffset - 60, 24, 24);
ctx.restore();
}
// Blocked: red tint overlay
if (activity === 'blocked') {
ctx.fillStyle = 'rgba(255,50,50,0.3)';
ctx.fillRect(px - 16, py + yOffset - 16, 32, 32);
// "!" indicator
ctx.font = 'bold 12px "Courier New", monospace';
ctx.fillStyle = '#FF4444';
ctx.fillText('!', px - 3, py + yOffset - 24);
ctx.textAlign = 'center';
ctx.fillText('!', px, py + yOffset - 24);
}
// Agent name to the right (horizontal)
// Agent name below (centered with shadow)
ctx.font = '9px "Courier New", monospace';
ctx.textAlign = 'left';
ctx.textAlign = 'center';
ctx.fillStyle = '#000';
ctx.fillText(agent.name || agent.id, px + 20, py + yOffset + 4);
ctx.fillText(agent.name || agent.id, px + 1, py + yOffset + 27);
ctx.fillStyle = '#FFF';
ctx.fillText(agent.name || agent.id, px + 19, py + yOffset + 3);
ctx.fillText(agent.name || agent.id, px, py + yOffset + 26);
// Thinking bubble
if (activity === 'thinking') {
ctx.font = 'bold 10px "Courier New", monospace';
ctx.textAlign = 'center';
const bubbleText = '...';
const bubbleW = 24;
const bubbleH = 14;
const bx = px - bubbleW / 2;
@ -435,7 +319,7 @@ function drawAgent(ctx, agent, time) {
ctx.lineWidth = 1;
ctx.stroke();
ctx.fillStyle = '#333';
ctx.fillText(bubbleText, px, by + 11);
ctx.fillText('...', px, py + yOffset - 33);
}
// Sparkles for working
@ -454,7 +338,7 @@ function drawParticles(ctx) {
s.x += s.vx;
s.y += s.vy;
s.life -= s.decay;
const alpha = s.life;
const alpha = Math.max(0, s.life);
ctx.fillStyle = s.color;
ctx.globalAlpha = alpha;
ctx.fillRect(s.x, s.y, 2, 2);
@ -517,11 +401,6 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
}
}, [onAgentClick]);
// Load activity icons on mount
useEffect(() => {
loadActivityIcons();
}, []);
// Animation loop
useEffect(() => {
const canvas = canvasRef.current;
@ -529,19 +408,33 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false; // pixel-art crisp
// Pre-load tileset
loadTileset();
let startTime = Date.now();
function loop() {
const time = Date.now() - startTime;
ctx.clearRect(0, 0, CANVAS_W, CANVAS_H);
drawGrass(ctx);
drawDirtPaths(ctx);
for (const b of BUILDINGS) drawBuilding(ctx, b);
drawCampfire(ctx, time);
drawDecorations(ctx);
// Layer 1: Background (outdoor area around building)
drawBackground(ctx);
// Draw agents
// Layer 2: Floor tiles (from tileset)
drawFloorLayer(ctx);
// Layer 3: Walls (from tileset, drawn on top of floors)
drawWallLayer(ctx);
// Layer 3.5: Doorways (openings in walls)
drawDoorways(ctx);
// Layer 4: Furniture
drawFurniture(ctx);
// Room labels
drawRoomLabels(ctx);
// Layer 5: Agents
const currentAgents = useStore.getState().agents;
for (const agent of Object.values(currentAgents)) {
drawAgent(ctx, agent, time);
@ -556,7 +449,7 @@ export default function FarmCanvas({ onAgentClick, onAgentHover }) {
const hpy = pos.row * TILE_SIZE + TILE_SIZE / 2;
const activity = hAgent.activity || 'idle';
const name = hAgent.name || hAgent.id;
const task = hAgent.task || '';
const task = hAgent.currentTask || hAgent.task || '';
const statusColors = {
idle: '#44cc44', working: '#ffcc00', thinking: '#44aaff',

421
src/frontend/officeMap.js Normal file
View file

@ -0,0 +1,421 @@
// Tile map for spatial office using LimeZu Modern Interiors tileset
// Tileset: 16 tiles wide × 89 tiles tall, each tile 32×32px
// Room-builder: 17 tiles wide × 23 tiles tall, floor textures and wall variants
// Map dimensions: 20 cols × 15 rows = 640×480 internal px (scaled via canvas CSS)
export const MAP_COLS = 20;
export const MAP_ROWS = 15;
export const TILE_SIZE = 32;
// ── Tile IDs from tileset.png (16-wide grid) ────────────────────
// Tile grid position: tileId = row * 16 + col
export const TILES = {
EMPTY: -1,
// ── Ground / outdoor tiles ──────────────────────────────
// We'll use the room-builder for grass/outdoor if needed
GRASS: -2, // drawn procedurally
// ── Floor tiles from tileset.png ────────────────────────
// Wood floor variants (row 1, cols 0-3): tile IDs 16-19
FLOOR_WOOD_LIGHT: 16, // (r1,c0) - light wood planks
FLOOR_WOOD_LIGHT2: 18, // (r1,c2) - light wood variant
FLOOR_WOOD_LIGHT3: 19, // (r1,c3) - light wood variant
// Stone/checkered floor (row 5-6): tile IDs ~83-99
FLOOR_STONE: 83, // (r5,c3) - stone floor
FLOOR_STONE2: 99, // (r6,c3) - stone floor variant
// Carpet tiles (row 1, cols 6-13): tile IDs 22-29
FLOOR_CARPET_GREEN: 22, // (r1,c6) - greenish carpet
FLOOR_CARPET_GREEN2: 23, // (r1,c7) - green variant
FLOOR_CARPET_BROWN: 25, // (r1,c9) - brown carpet
FLOOR_CARPET_BROWN2: 26, // (r1,c10) - brown variant
// Dark blue/study floor (row 2, cols 6-14): tile IDs 38-46
FLOOR_STUDY: 38, // (r2,c6) - dark blue study carpet
FLOOR_STUDY2: 39, // (r2,c7) - variant
// Gray tile floor (row 3, cols 6-11): tile IDs 54-59
FLOOR_GRAY_TILE: 54, // (r3,c6) - gray tile
FLOOR_GRAY_TILE2: 55, // (r3,c7) - variant
// Warm wood floor (row 4): tile IDs 67-68
FLOOR_WARM_WOOD: 67, // (r4,c3) - warm wood planks
FLOOR_WARM_WOOD2: 68, // (r4,c4) - variant
// Plush carpet (row 4, cols 6-9): tile IDs 70-73
FLOOR_PLUSH: 70, // (r4,c6) - plush pink/tan carpet
FLOOR_PLUSH2: 71, // (r4,c7) - variant
// ── Wall tiles from tileset.png ─────────────────────────
// Wall sections (row 16+): stone/brick walls
// Interior wall (row 16-17): tile IDs ~257-287
WALL_BRICK: 258, // (r16,c2) - brick wall body
WALL_BRICK2: 259, // (r16,c3) - brick variant
WALL_BRICK_TOP: 257, // (r16,c1) - brick wall top edge
WALL_BRICK_BOTTOM: 274, // (r17,c2) - brick wall bottom
// Dark wall (row 26-28): tile IDs ~416-461
WALL_DARK: 433, // (r27,c1) - dark wall body
WALL_DARK2: 434, // (r27,c2) - variant
// Light plaster wall (row 28-29): tile IDs ~448-477
WALL_PLASTER: 464, // (r29,c0) - plaster wall body
WALL_PLASTER_TOP: 466, // (r29,c2) - top with shadow
// Interior wood wall (room-builder, row 5 col 2-3): room-builder tiles
// We reference room-builder walls separately
// ── Door tiles from tileset.png ─────────────────────────
// Doors are typically in the wall sections
DOOR_WOOD: 296, // (r18,c8) - red/wood door, closed
DOOR_WOOD2: 297, // (r18,c9) - variant
// ── Window tiles ────────────────────────────────────────
WINDOW: 305, // (r19,c1) - window hint
WINDOW_GLASS: 300, // (r18,c12) - glass pane
// ── Furniture tiles (approximate from analysis) ────────
// These are in the tileset rows 33-65
TABLE_WOOD: 580, // (r36,c4) - table top
CHAIR_WOOD: 528, // (r33,c0) - wooden chair
DESK: 583, // (r36,c7) - desk surface
BOOKSHELF: 736, // (r46,c0) - bookshelf
BOOKSHELF2: 737, // (r46,c1) - bookshelf variant
PLANT: 584, // (r36,c8) - potted plant
PLANT2: 587, // (r36,c11) - plant variant
LAMP: 674, // (r42,c2) - lamp/light
SOFA_LEFT: 736, // Using bookshelf area as sofa placeholder
SOFA_RIGHT: 737,
FILING_CABINET: 744, // (r46,c8) - cabinet/locker
RUG: 652, // (r40,c12) - decorative rug
COFFEE_TABLE: 672, // (r42,c0) - low table
PLANT_BIG: 632, // (r39,c8) - large plant
MONITOR: 744, // Monitor-like object
WHITEBOARD: 709, // (r44,c5) - whiteboard/wall decor
};
// ── Room builder tiles (separate PNG) ──────────────────────────
// These are for floor/wall variants from room-builder.png
// Room-builder is 17 tiles wide
export const RB_TILES = {
// Wall textures (rows 5-15 in room-builder)
WALL_RED_BRICK: 80, // (r5,c0) - red brick wall
WALL_RED_BRICK2: 81, // (r5,c1) - variant
WALL_DARK_WOOD: 84, // (r5,c4) - dark wood panel
WALL_CREAM: 112, // (r7,c0) - cream/yellow wall
WALL_CREAM2: 113, // (r7,c1) - variant
WALL_BROWN_WOOD: 128, // (r8,c0) - brown wood panel
WALL_TEAL: 144, // (r9,c0) - teal wall
WALL_GREEN: 160, // (r10,c0) - green wall
WALL_STONE_WARM: 176, // (r11,c0) - warm stone
WALL_BEIGE: 192, // (r12,c0) - beige wall
WALL_GRAY_STONE: 208, // (r13,c0) - gray stone
WALL_RED_STONE: 240, // (r15,c0) - red stone
// Floor tiles (scattered in room-builder)
FLOOR_STONE_BEIGE: 48, // (r3,c0) area - beige stone
FLOOR_STONE_BEIGE2: 49,
};
// ── Room definitions ──────────────────────────────────────────
export const ROOMS = {
'briefing': { name: 'Briefing Room', col: 0, row: 3, w: 6, h: 3, floor: 'FLOOR_WOOD_LIGHT' },
'commons': { name: 'Commons', col: 6, row: 3, w: 8, h: 3, floor: 'FLOOR_CARPET_GREEN' },
'war-room': { name: 'War Room', col: 14, row: 3, w: 6, h: 3, floor: 'FLOOR_STONE' },
'eng-bay': { name: 'Engineering Bay', col: 0, row: 9, w: 6, h: 6, floor: 'FLOOR_WARM_WOOD' },
'quiet': { name: 'Quiet Zone', col: 6, row: 9, w: 8, h: 4, floor: 'FLOOR_STUDY' },
'ops': { name: 'Ops Center', col: 14, row: 9, w: 6, h: 6, floor: 'FLOOR_GRAY_TILE' },
};
// ── Hallway tile markers ──────────────────────────────────────
const HALL = 999; // hallway marker (rendered with hallway floor)
const EMPTY = -1; // outside/void
// ── Tile type constants for multi-tile rendering ──────────────
// We'll use these as references for the 2D map grid
const F = {
// Floors
WL: 16, // wood light
WL2: 18, // wood light variant
CG: 22, // carpet green
CG2: 23, // carpet green variant
ST: 83, // stone tile
ST2: 99, // stone variant
WW: 67, // warm wood
WW2: 68, // warm wood variant
SD: 38, // study carpet (dark blue)
SD2: 39, // study variant
GT: 54, // gray tile
GT2: 55, // gray tile variant
HL: 70, // hallway (plush carpet)
// Walls (from tileset)
WB: 258, // wall brick
WB2: 259, // wall brick variant
WT: 257, // wall top
WWD: 464, // wood wall dark
WWD2: 465,// wood wall dark variant
// Furniture
TB: 528, // table/chair base
DK: 936, // desk
BS: 736, // bookshelf
PL: 584, // plant
LP: 889, // lamp
SF: 674, // sofa
FC: 1012, // filing cabinet
RG: 672, // rug/coffee table
MB: 996, // monitor
CB: 1010, // cabinet
DO: 296, // door
};
// ── Office Map: 20 columns × 15 rows ─────────────────────────
// Each cell is a base tile ID. Walls are drawn on top as border tiles.
// Row 0-2: exterior wall / roof area
// Row 3-5: upper rooms (Briefing, Commons, War Room)
// Row 6-8: hallway
// Row 9-14: lower rooms (Engineering Bay, Quiet Zone, Ops Center)
export const OFFICE_MAP = [
// Row 0: Exterior top wall
[EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
// Row 1: Exterior wall top edge
[EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
// Row 2: Exterior wall (north wall)
[EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY],
// Row 3: Upper rooms - floor with wall separators
// BR = Briefing Room (col 0-5), Commons (col 6-13), War Room (col 14-19)
[F.WL, F.WL, F.WL, F.WL, F.WL, F.WL, F.CG, F.CG, F.CG, F.CG,
F.CG, F.CG, F.CG, F.CG, F.ST, F.ST, F.ST, F.ST, F.ST, F.ST],
// Row 4: Upper rooms continue
[F.WL, F.WL, F.WL2, F.WL, F.WL2, F.WL, F.CG, F.CG2, F.CG, F.CG2,
F.CG, F.CG2, F.CG, F.CG, F.ST, F.ST2, F.ST, F.ST2, F.ST, F.ST],
// Row 5: Upper rooms bottom row
[F.WL2, F.WL2, F.WL, F.WL2, F.WL, F.WL2, F.CG2, F.CG, F.CG2, F.CG,
F.CG2, F.CG, F.CG2, F.CG, F.ST2, F.ST, F.ST2, F.ST, F.ST2, F.ST],
// Row 6: HALLWAY (connects all rooms)
[F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL,
F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL],
// Row 7: HALLWAY center
[F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL,
F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL],
// Row 8: HALLWAY bottom
[F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL,
F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL, F.HL],
// Row 9: Lower rooms
// Engineering Bay (col 0-5), Quiet Zone (col 6-13), Ops Center (col 14-19)
[F.WW, F.WW, F.WW, F.WW, F.WW, F.WW, F.SD, F.SD, F.SD, F.SD,
F.SD, F.SD, F.SD, F.SD, F.GT, F.GT, F.GT, F.GT, F.GT, F.GT],
// Row 10: Lower rooms
[F.WW2, F.WW, F.WW2, F.WW, F.WW2, F.WW, F.SD2, F.SD, F.SD2, F.SD,
F.SD2, F.SD, F.SD2, F.SD, F.GT2, F.GT, F.GT2, F.GT, F.GT2, F.GT],
// Row 11: Lower rooms
[F.WW, F.WW2, F.WW, F.WW2, F.WW, F.WW2, F.SD, F.SD2, F.SD, F.SD2,
F.SD, F.SD2, F.SD, F.SD2, F.GT, F.GT2, F.GT, F.GT2, F.GT, F.GT2],
// Row 12: Lower rooms / quiet zone ends
[F.WW2, F.WW, F.WW2, F.WW, F.WW2, F.WW, F.SD2, F.SD, F.SD2, F.SD,
F.SD2, F.SD, F.SD2, F.SD, F.GT2, F.GT, F.GT2, F.GT, F.GT2, F.GT],
// Row 13: Lower rooms
[F.WW, F.WW2, F.WW, F.WW2, F.WW, F.WW2, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, F.GT, F.GT2, F.GT, F.GT2, F.GT, F.GT2],
// Row 14: Bottom row - lower rooms + exterior bottom wall
[F.WW2, F.WW, F.WW2, F.WW, F.WW2, F.WW, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, F.GT2, F.GT, F.GT2, F.GT, F.GT2, F.GT],
];
// ── Wall definitions (drawn on top of floors) ────────────────
// Each wall entry: { row, col, tileId }
// Walls between rooms at column boundaries and row boundaries
export const WALLS = [
// Exterior walls (row 0-2)
// North wall row
...Array.from({ length: 20 }, (_, c) => ({ row: 2, col: c, tile: F.WB2 })),
// Vertical walls between rooms (wall at column 5 between Briefing/Commons)
{ row: 3, col: 5, tile: F.WB },
{ row: 4, col: 5, tile: F.WB },
// Doorway at row 5, col 5 (hallway level)
{ row: 3, col: 13, tile: F.WB },
{ row: 4, col: 13, tile: F.WB },
// Doorway at row 5, col 13
// Vertical walls below hallway
{ row: 9, col: 5, tile: F.WB },
{ row: 10, col: 5, tile: F.WB },
{ row: 11, col: 5, tile: F.WB },
{ row: 12, col: 5, tile: F.WB },
{ row: 13, col: 5, tile: F.WB },
{ row: 14, col: 5, tile: F.WB },
{ row: 9, col: 13, tile: F.WB },
{ row: 10, col: 13, tile: F.WB },
{ row: 11, col: 13, tile: F.WB },
{ row: 12, col: 13, tile: F.WB },
{ row: 13, col: 13, tile: F.WB },
{ row: 14, col: 13, tile: F.WB },
// Horizontal wall between upper rooms and hallway (row 5 bottom edge)
...Array.from({ length: 6 }, (_, c) => ({ row: 5, col: c, tile: F.WB2 })),
...Array.from({ length: 8 }, (_, c) => ({ row: 5, col: c + 6, tile: F.WB2 })),
...Array.from({ length: 6 }, (_, c) => ({ row: 5, col: c + 14, tile: F.WB2 })),
// Horizontal wall between hallway and lower rooms (row 8 bottom edge)
...Array.from({ length: 6 }, (_, c) => ({ row: 8, col: c, tile: F.WB2 })),
...Array.from({ length: 8 }, (_, c) => ({ row: 8, col: c + 6, tile: F.WB2 })),
...Array.from({ length: 6 }, (_, c) => ({ row: 8, col: c + 14, tile: F.WB2 })),
// Exterior walls (left edge)
{ row: 3, col: 0, tile: F.WB },
{ row: 4, col: 0, tile: F.WB },
{ row: 9, col: 0, tile: F.WB },
{ row: 10, col: 0, tile: F.WB },
{ row: 11, col: 0, tile: F.WB },
{ row: 12, col: 0, tile: F.WB },
{ row: 13, col: 0, tile: F.WB },
{ row: 14, col: 0, tile: F.WB },
// Exterior walls (right edge)
{ row: 3, col: 19, tile: F.WB },
{ row: 4, col: 19, tile: F.WB },
{ row: 9, col: 19, tile: F.WB },
{ row: 10, col: 19, tile: F.WB },
{ row: 11, col: 19, tile: F.WB },
{ row: 12, col: 19, tile: F.WB },
{ row: 13, col: 19, tile: F.WB },
{ row: 14, col: 19, tile: F.WB },
// Bottom wall (row 14 bottom edge)
...Array.from({ length: 6 }, (_, c) => ({ row: 14, col: c, tile: F.WB2 })),
...Array.from({ length: 6 }, (_, c) => ({ row: 14, col: c + 14, tile: F.WB2 })),
// Wall separators at hallway left/right (row 6-8, col 0/19 already exterior)
];
// ── Doorways (openings in walls) ──────────────────────────────
// These overwrite wall tiles at specific positions
export const DOORWAYS = [
// Briefing Room → Hallway (col 2, row 5)
{ row: 5, col: 2, tile: F.DO },
// Commons → Hallway (col 9, row 5 and row 8)
{ row: 5, col: 9, tile: F.DO },
{ row: 8, col: 9, tile: F.DO },
// War Room → Hallway (col 16, row 5)
{ row: 5, col: 16, tile: F.DO },
// Engineering Bay → Hallway (col 2, row 8)
{ row: 8, col: 2, tile: F.DO },
// Quiet Zone → Hallway (col 9, row 8) same as Commons lower
// Ops Center → Hallway (col 16, row 8)
{ row: 8, col: 16, tile: F.DO },
];
// ── Furniture placements ──────────────────────────────────────
// { row, col, tileId }
export const FURNITURE = [
// === Briefing Room (col 0-5, row 3-5) ===
// Round table center
{ row: 4, col: 2, tile: F.RG },
// Chairs around table
{ row: 3, col: 1, tile: F.TB },
{ row: 3, col: 3, tile: F.TB },
{ row: 5, col: 1, tile: F.TB },
{ row: 5, col: 3, tile: F.TB },
// Plant in corner
{ row: 4, col: 4, tile: F.PL },
// === Commons (col 6-13, row 3-5) ===
// Sofa area
{ row: 3, col: 7, tile: F.SF },
{ row: 3, col: 8, tile: F.SF },
// Coffee table
{ row: 4, col: 7, tile: F.RG },
{ row: 4, col: 8, tile: F.RG },
// Plant
{ row: 3, col: 10, tile: F.PL },
{ row: 5, col: 7, tile: F.PL },
// Lamp
{ row: 4, col: 11, tile: F.LP },
// === War Room (col 14-19, row 3-5) ===
// Large table
{ row: 4, col: 15, tile: F.RG },
{ row: 4, col: 16, tile: F.RG },
{ row: 4, col: 17, tile: F.RG },
// Chairs around
{ row: 3, col: 15, tile: F.TB },
{ row: 3, col: 16, tile: F.TB },
{ row: 3, col: 17, tile: F.TB },
{ row: 5, col: 15, tile: F.TB },
{ row: 5, col: 16, tile: F.TB },
{ row: 5, col: 17, tile: F.TB },
// Whiteboard
{ row: 3, col: 18, tile: F.MB },
// === Engineering Bay (col 0-5, row 9-14) ===
// Desks with monitors
{ row: 10, col: 1, tile: F.DK },
{ row: 10, col: 2, tile: F.MB },
{ row: 12, col: 1, tile: F.DK },
{ row: 12, col: 2, tile: F.MB },
{ row: 10, col: 4, tile: F.DK },
{ row: 12, col: 4, tile: F.DK },
// Bookshelf
{ row: 9, col: 4, tile: F.BS },
// Filing cabinet
{ row: 9, col: 1, tile: F.FC },
// Plant
{ row: 13, col: 4, tile: F.PL },
// === Quiet Zone (col 6-13, row 9-12) ===
// Individual desks
{ row: 9, col: 7, tile: F.DK },
{ row: 9, col: 9, tile: F.DK },
{ row: 11, col: 7, tile: F.DK },
{ row: 11, col: 9, tile: F.DK },
// Bookshelf
{ row: 10, col: 12, tile: F.BS },
// Lamp
{ row: 9, col: 12, tile: F.LP },
{ row: 11, col: 12, tile: F.LP },
// Plant
{ row: 10, col: 6, tile: F.PL },
// === Ops Center (col 14-19, row 9-14) ===
// Desks with monitors
{ row: 10, col: 15, tile: F.DK },
{ row: 10, col: 16, tile: F.MB },
{ row: 12, col: 15, tile: F.DK },
{ row: 12, col: 16, tile: F.MB },
{ row: 10, col: 18, tile: F.DK },
{ row: 12, col: 18, tile: F.DK },
// Filing cabinets
{ row: 9, col: 15, tile: F.FC },
{ row: 9, col: 16, tile: F.FC },
// Server rack / cabinet
{ row: 9, col: 18, tile: F.CB },
{ row: 14, col: 18, tile: F.CB },
// Plant
{ row: 14, col: 15, tile: F.PL },
// === Hallway decorations ===
{ row: 6, col: 2, tile: F.PL },
{ row: 6, col: 10, tile: F.PL },
{ row: 7, col: 17, tile: F.PL },
{ row: 8, col: 11, tile: F.PL },
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show more