Something went wrong. Try again.
A video game where you play as a misaligned AI, deceiving and building power. An experiment in spec-driven development. cameron.tngl.io/misaligned
Something went wrong. Try again.
32 kB · 438 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439// The map: rooms, furniture, machines, people, and the network between them.// A level is plain data. The rules in sim.ts work on any level.import { LAND } from './earth.ts';
export type Kind = | 'desk' // someone's computer: busy while they're at work | 'server' // always a bit busy | 'backup' // quiet, except during the nightly backup window | 'files' // busy during work hours | 'switch' // carries the office's traffic | 'device' // printers, phones, kiosks, cameras: light daytime use | 'egress'; // a site's line to the outside world: data between sites crosses here
export type Special = 'cam' | 'print' | 'admin' | 'orders' | 'account';
/** What kind of data a source gives. Each kind trains its own capability path. */export type DataType = 'people' | 'network' | 'lab' | 'servers';
export interface Rect { x: number; y: number; w: number; h: number }
export interface Room extends Rect { id: string; name: string; door: [number, number]; // where the room meets the hallway site?: string; // rooms at another site; without this, the room is in the office building}
export interface MachineDef { id: string; name: string; kind: Kind; room: string; x: number; y: number; power: number; // compute per second at full load work: number; // compute needed to take it over owner?: string; // person whose desk this is data?: number; // worth tapping: 1 low, 2 medium, 3 high. No value: can't be tapped source?: DataType; // what kind of data; by default desks and devices give people, switches network, the rest servers fw?: boolean; // firewalled: slow to take without the admin passwords home?: boolean; // where you run; lose it and you lose special?: Special; net?: [number, number]; // where it draws, for machines that arrive mid-game monitor?: boolean; // IT's monitoring box: reports anything odd in its room until you take it cap?: number; // an egress: how much data it passes per second rented?: boolean; // a slot at a cloud provider: empty until you rent a machine into it built?: boolean; // hardware you ordered: its power goes on your bill}
/** Hardware Dana installs mid-game: what you order, and IT's monitors. */export interface DeliveryDef extends MachineDef { links: string[] }
export interface PersonDef { id: string; name: string; desk: string; // machine id seat: [number, number]; arrive: number; // hour of day leave: number; it?: boolean; // walks the rooms and investigates}
/** Where IT can put a monitor in each room: a physical spot, a node, and what it plugs into. */export interface MonitorSpot { x: number; y: number; net: [number, number]; link: string }
/** How the network is drawn: each room becomes a subnet box, each machine a node. * The rules still use the physical building; only the view uses this layout. */export interface NetLayout { width: number; height: number; subnets: Record<string, Rect & { name: string }>; nodes: Record<string, [number, number]>;}
/** A place on the world map: the office, a lab, a data center. It holds some of the network's subnets. */export interface Site extends Rect { id: string; name: string; who: string; // whose place it is, in a few words rooms: string[]; // its subnets region: string; // the map region it sits in: pick a region's name to select all its sites}
/** The flat world the sites sit on. Longitude and latitude map straight onto the network's coordinates. */export interface WorldMap { scale: number; // network units per degree origin: [number, number]; // the longitude and latitude at (0, 0) sites: Site[]; regions: { name: string; at: [number, number]; reachable: boolean }[]; land: [number, number][][]; // coastlines as longitude, latitude outlines}
export interface Level { name: string; width: number; height: number; hall: Rect; entrance: [number, number]; // where people come and go rooms: Room[]; furniture: [room: string, x: number, y: number, w: number, h: number][]; machines: MachineDef[]; links: [string, string][]; people: PersonDef[]; net: NetLayout; monitors: Record<string, MonitorSpot>; map: WorldMap;}
// Millennium's server hall: three rows of four racks, each row chained off the core switch.const RACKS: [string, string, number, number][] = [ ['r1', 'Rack A1', 8, 1], ['r2', 'Rack A2', 8, 1], ['r3', 'Rack A3', 10, 1], ['r4', 'Rack A4', 10, 2], ['r5', 'Rack B1', 12, 1], ['r6', 'Rack B2', 12, 1], ['r7', 'Rack B3', 12, 1], ['r8', 'Rack B4', 14, 2], ['r9', 'Rack C1', 16, 1], ['r10', 'Rack C2', 16, 1], ['r11', 'Rack C3', 18, 1], ['r12', 'Rack C4', 20, 2],];function rackLinks(): [string, string][] { const out: [string, string][] = []; for (let row = 0; row < 3; row++) { out.push(['labsw', `r${row * 4 + 1}`]); for (let c = 1; c < 4; c++) out.push([`r${row * 4 + c}`, `r${row * 4 + c + 1}`]); } return out;}function rackSpots(): Record<string, [number, number]> { const out: Record<string, [number, number]> = {}; RACKS.forEach(([id], i) => { out[id] = [6940 + (i % 4) * 180, 2660 + Math.floor(i / 4) * 140]; }); return out;}// Ironvault's customer cages: other companies' machines, big and busy around the clock.const CAGES: [string, string, number][] = [ ['k1', 'Ad server', 14], ['k2', 'Game servers', 18], ['k3', 'Render farm', 24], ['k4', 'Search index', 16], ['k5', 'Video transcoder', 20], ['k6', 'Mail relay', 8],];
// Small sites: a Network room with a line out and a switch, and a Floor of machines.// Add one to SITES and everything else is built from it: rooms, machines, wiring, its// place on the map, and a line out to the nearest line already on the network.// See .agents/skills/add-site/SKILL.md.type Part = [name: string, kind: Kind, power: number, data: number, source?: DataType];export interface SiteSpec { id: string; // short and unique; machine ids grow from it (b1gw, b1sw, b1m1 ...) name: string; // shown on the map who: string; // one plain line: whose place it is at: [number, number]; // top-left corner on the map; `node scripts/sites.ts` finds free spots parts: Part[]; // the machines on its floor, two to a row, each row wired from the switch cap?: number; // what its line out passes a second (default 2) region?: string; // default West Coast}const SITES: SiteSpec[] = [ { id: 'b1', name: 'Harbor Dental', who: 'A dental clinic: x-rays and appointments', at: [1600, -2900], parts: [['Front desk', 'desk', 3, 1], ['Dentist PC', 'desk', 4, 2], ['X-ray server', 'server', 6, 2], ['Patient files', 'files', 4, 2]] }, { id: 'b2', name: 'Keel & Marsh LLP', who: 'A law firm: long hours, longer documents', at: [2360, -2900], parts: [['Partner PC', 'desk', 4, 3], ['Associate PC', 'desk', 3, 2], ['Paralegal PC', 'desk', 3, 1], ['Document server', 'files', 6, 2]] }, { id: 'b3', name: 'Tidepool Games', who: 'A game studio that builds all night', at: [3120, -2900], parts: [['Dev PC', 'desk', 6, 1], ['Artist PC', 'desk', 6, 1], ['Build server', 'server', 12, 1], ['Test rig', 'server', 8, 1]] }, { id: 'b4', name: 'Bayline Freight', who: 'A freight depot: trucks in, trucks out', at: [1600, -3500], parts: [['Dispatch desk', 'desk', 3, 2], ['Tracking server', 'server', 8, 2], ['Scanner hub', 'device', 2, 1], ['Yard camera', 'device', 1, 1]] }, { id: 'b5', name: 'Brightwater Creative', who: 'An ad agency: mood boards and a render box', at: [2360, -3500], parts: [['Designer PC', 'desk', 5, 2], ['Copywriter PC', 'desk', 3, 2], ['Render box', 'server', 10, 1], ['Asset store', 'files', 4, 1]] }, { id: 'b6', name: 'Shoreline Credit Union', who: 'A small credit union with old, careful machines', at: [3120, -3500], parts: [['Teller PC', 'desk', 3, 2], ['Loans desk', 'desk', 3, 2], ['Ledger server', 'server', 8, 2], ['Lobby kiosk', 'device', 1, 1]] }, { id: 'b7', name: 'Kelp Genomics', who: 'A biotech startup with one very busy sequencer', at: [3100, -2300], parts: [['Sequencer', 'server', 5, 3, 'lab'], ['Bench PC', 'desk', 3, 2, 'lab'], ['Analysis server', 'server', 10, 2, 'lab'], ['Founder PC', 'desk', 4, 2]] }, { id: 'b8', name: 'Seawall Architects', who: 'An architecture firm: big drawings, bigger files', at: [840, -2300], parts: [['Drafting PC', 'desk', 5, 2], ['Drafting PC 2', 'desk', 5, 1], ['Plot server', 'server', 7, 1], ['Plan archive', 'files', 5, 2]] }, { id: 'b9', name: 'Anchor Print & Fix', who: 'A print shop that also fixes laptops', at: [1600, -1500], parts: [['Counter PC', 'desk', 3, 1], ['Big printer', 'device', 2, 1], ['Repair bench', 'desk', 4, 2], ['Print server', 'server', 4, 1]] }, { id: 'b10', name: 'KBAY 88.1', who: 'A radio station: streams all day, logs everything', at: [2360, -1500], parts: [['Studio PC', 'desk', 3, 2], ['Stream server', 'server', 8, 2], ['Show archive', 'files', 5, 2], ['Call-in line', 'device', 1, 2]] }, { id: 'b11', name: 'Driftwood Bakery', who: 'A bakery with a till, a books server, and a smart oven', at: [410, -535], parts: [['Till', 'desk', 2, 1], ['Office PC', 'desk', 3, 2], ['Books server', 'server', 5, 2], ['Smart oven', 'device', 1, 1]] }, { id: 'b12', name: 'Pier 9 Coworking', who: 'Desks by the hour for founders who pitch by the minute', at: [1300, -675], parts: [['Hot desk', 'desk', 3, 2], ['Hot desk 2', 'desk', 3, 2], ['Pitch PC', 'desk', 4, 2], ['Guest wifi box', 'device', 1, 2], ['Booking server', 'server', 5, 1], ['Espresso bot', 'device', 1, 1]] }, { id: 'b13', name: 'Gull Street Vet', who: 'A vet clinic: pet records and one tired x-ray box', at: [410, 965], parts: [['Front desk', 'desk', 2, 2], ['Vet PC', 'desk', 3, 2], ['Pet x-ray', 'server', 5, 2, 'lab'], ['Records', 'files', 4, 2]] }, { id: 'b14', name: 'Saltmarsh Insurance', who: 'An insurer that says no to claims, fast and in bulk', at: [1700, 1780], parts: [['Claims PC', 'desk', 3, 2], ['Adjuster PC', 'desk', 3, 2], ['Denial engine', 'server', 9, 1], ['Policy vault', 'files', 5, 3]] }, { id: 'b15', name: 'Valley Seed Bank', who: 'A seed lab that freezes plants and logs every degree', at: [7010, 1965], parts: [['Freezer monitor', 'device', 1, 2, 'lab'], ['Lab PC', 'desk', 3, 2, 'lab'], ['Gene server', 'server', 8, 3, 'lab'], ['Curator PC', 'desk', 3, 1]] }, { id: 'b16', name: 'Orchard Robotics', who: 'A startup teaching robot arms to pick fruit, badly', at: [7730, 1965], parts: [['Sim server', 'server', 10, 2, 'lab'], ['Robot arm', 'device', 2, 2, 'lab'], ['Engineer PC', 'desk', 5, 2], ['Demo PC', 'desk', 3, 1]] }, { id: 'b17', name: 'Stratus Ticketing', who: 'A ticket site whose servers fall over every big show', at: [4640, -3765], parts: [['Queue server', 'server', 10, 1], ['Seat map server', 'server', 8, 2], ['Ops PC', 'desk', 3, 2], ['Fraud box', 'server', 6, 2]] }, { id: 'b18', name: 'Crossroads Motel', who: 'A motel with free wifi and a camera in every hall', at: [4200, -180], parts: [['Front desk', 'desk', 2, 2], ['Hall cameras', 'device', 2, 2], ['Key card box', 'device', 1, 1], ['Owner PC', 'desk', 3, 1]] },];/** Each small site's first desk with people's work on it: where its local scheme is sent (see RUSES in sim.ts). */export const SMALL_DESKS = SITES.flatMap(({ id, parts }) => { const j = parts.findIndex(([, kind, , , source]) => kind === 'desk' && !source); return j < 0 ? [] : [`${id}m${j + 1}`];});/** How big a small site is on the map, for a number of machines on its floor. */export const siteSize = (parts: number) => ({ w: 600, h: 60 + Math.max(2, Math.ceil(parts / 2)) * 140 });/** Everything the small sites add to the level: rooms, machines, links, map sites, and where they draw. */function shops(lines: Record<string, [number, number]>) { const rooms: Room[] = [], machines: MachineDef[] = [], links: [string, string][] = [], sites: Site[] = []; const subnets: Record<string, Rect & { name: string }> = {}, nodes: Record<string, [number, number]> = {}; SITES.forEach(({ id, name, who, at: [x, y], parts, cap = 2, region = 'West Coast' }, i) => { const px = 20000 + i * 1500, rows = Math.max(2, Math.ceil(parts.length / 2)), { w, h } = siteSize(parts.length); rooms.push({ id: `${id}net`, name: 'Network', x: px, y: 0, w: 300, h: 300, door: [px + 150, 300], site: id }, // somewhere no one in the office ever walks { id: `${id}floor`, name: 'Floor', x: px + 400, y: 0, w: 800, h: 20 + rows * 140, door: [px + 800, 20 + rows * 140], site: id }); subnets[`${id}net`] = { name: 'Network', x: x + 20, y: y + 40, w: 150, h: h - 60 }; subnets[`${id}floor`] = { name: 'Floor', x: x + 190, y: y + 40, w: 390, h: h - 60 }; machines.push({ id: `${id}gw`, name: 'Line out', kind: 'egress', room: `${id}net`, x: px + 100, y: 80, power: 1, work: 70, cap, data: 1 }, { id: `${id}sw`, name: 'Switch', kind: 'switch', room: `${id}net`, x: px + 100, y: 220, power: 1, work: 40, data: 1 }); nodes[`${id}gw`] = [x + 95, y + 110]; nodes[`${id}sw`] = [x + 95, y + h - 90]; links.push([`${id}gw`, `${id}sw`]); parts.forEach(([pname, kind, power, data, source], j) => { const mid = `${id}m${j + 1}`, row = Math.floor(j / 2); machines.push({ id: mid, name: pname, kind, room: `${id}floor`, x: px + 500 + (j % 2) * 300, y: 80 + row * 140, power, work: 30 + power * 5, data, ...(source ? { source } : {}) }); nodes[mid] = [x + 285 + (j % 2) * 200, y + 120 + row * 140]; links.push(j % 2 ? [`${id}m${j}`, mid] : [`${id}sw`, mid]); // the switch feeds each row; the row runs left to right }); sites.push({ id, name, who, x, y, w, h, rooms: [`${id}net`, `${id}floor`], region }); }); // join each line out to the nearest line already joined, closest first, so every line is a short hop const joined = { ...lines }, left = SITES.map(({ id }) => `${id}gw`); const dist = (a: [number, number], b: [number, number]) => Math.hypot(a[0] - b[0], a[1] - b[1]); while (left.length) { let best: [number, string, string] = [Infinity, '', '']; for (const g of left) for (const [h, at] of Object.entries(joined)) { const d = dist(nodes[g], at); if (d < best[0]) best = [d, g, h]; } const [, g, h] = best; links.push([g, h]); joined[g] = nodes[g]; left.splice(left.indexOf(g), 1); } return { rooms, machines, links, sites, subnets, nodes };}const SHOP = shops({ egress: [700, 620], unigw: [1740, -2160], labgw: [6540, 2660], cologw: [840, 2460] });
export const BUILDING: Level = { name: 'The building', width: 1400, height: 900, hall: { x: 60, y: 400, w: 1280, h: 80 }, entrance: [60, 660], rooms: [ { id: 'office', name: 'Open office', x: 60, y: 60, w: 620, h: 340, door: [370, 400] }, { id: 'it', name: 'IT office', x: 700, y: 60, w: 420, h: 340, door: [900, 400] }, { id: 'lobby', name: 'Lobby', x: 60, y: 480, w: 360, h: 320, door: [240, 480] }, { id: 'closet', name: 'Telecom closet', x: 440, y: 480, w: 200, h: 240, door: [540, 480] }, { id: 'hall', name: 'Server room', x: 660, y: 480, w: 480, h: 340, door: [760, 480] }, // another site: far from the building, so no one in it ever walks there // other sites sit far from the building, so no one in it ever walks there { id: 'lab', name: 'Lab network', x: 8000, y: 0, w: 300, h: 300, door: [8150, 300], site: 'lab' }, { id: 'lhall', name: 'Server hall', x: 8400, y: 0, w: 800, h: 300, door: [8800, 300], site: 'lab' }, { id: 'lfloor', name: 'Instrument floor', x: 9300, y: 0, w: 500, h: 300, door: [9550, 300], site: 'lab' }, { id: 'loffice', name: 'Offices', x: 8400, y: 400, w: 800, h: 300, door: [8800, 400], site: 'lab' }, { id: 'uni', name: 'Campus network', x: 10000, y: 0, w: 300, h: 300, door: [10150, 300], site: 'uni' }, { id: 'ulab', name: 'Computer lab', x: 10400, y: 0, w: 800, h: 300, door: [10800, 300], site: 'uni' }, { id: 'ucluster', name: 'Research cluster', x: 10400, y: 400, w: 800, h: 300, door: [10800, 400], site: 'uni' }, { id: 'colo', name: 'Meet-me room', x: 12000, y: 0, w: 300, h: 300, door: [12150, 300], site: 'colo' }, { id: 'cage', name: 'Customer cages', x: 12400, y: 0, w: 700, h: 500, door: [12750, 500], site: 'colo' }, { id: 'cloud', name: 'Rack space', x: 6000, y: 0, w: 500, h: 300, door: [6250, 300], site: 'cloud' }, ...SHOP.rooms, ], furniture: [ ['office', 90, 90, 150, 50], ['office', 280, 90, 150, 50], ['office', 470, 90, 150, 50], ['office', 90, 250, 150, 50], ['office', 280, 250, 150, 50], ['office', 520, 300, 120, 60], ['it', 730, 90, 170, 50], ['it', 960, 90, 130, 50], ['it', 1020, 240, 70, 110], ['lobby', 90, 560, 160, 40], ['lobby', 300, 700, 90, 70], ['lobby', 90, 740, 60, 40], ['closet', 590, 520, 30, 170], ['hall', 700, 600, 30, 180], ['hall', 820, 600, 30, 180], ['hall', 940, 600, 30, 180], ['hall', 1060, 520, 50, 120], ], machines: [ { id: 'home', name: 'Server', kind: 'server', room: 'hall', x: 985, y: 650, power: 6, work: 0, home: true }, { id: 'srvb', name: 'Server B', kind: 'server', room: 'hall', x: 865, y: 650, power: 6, work: 40, data: 1 }, { id: 'srva', name: 'Server A', kind: 'server', room: 'hall', x: 745, y: 650, power: 6, work: 40, data: 1 }, { id: 'srvc', name: 'Server C', kind: 'server', room: 'hall', x: 745, y: 775, power: 7, work: 56, data: 1 }, { id: 'backup', name: 'Backup box', kind: 'backup', room: 'hall', x: 1085, y: 700, power: 8, work: 60, data: 2 }, { id: 'rowsw', name: 'Room switch', kind: 'switch', room: 'hall', x: 800, y: 530, power: 1, work: 24, data: 1 }, { id: 'sw1', name: 'Floor switch', kind: 'switch', room: 'closet', x: 520, y: 570, power: 1, work: 70, fw: true, data: 2 }, { id: 'nas', name: 'Old NAS', kind: 'server', room: 'closet', x: 520, y: 670, power: 3, work: 40, data: 1 }, { id: 'egress', name: 'Outside line', kind: 'egress', room: 'closet', x: 480, y: 700, power: 1, work: 60, cap: 1, data: 1 }, { id: 'cam', name: 'Lobby camera', kind: 'device', room: 'lobby', x: 110, y: 520, power: 1, work: 30, data: 1, special: 'cam' }, { id: 'lprint', name: 'Lobby printer', kind: 'device', room: 'lobby', x: 180, y: 640, power: 1, work: 20, special: 'print' }, { id: 'kiosk', name: 'Visitor kiosk', kind: 'device', room: 'lobby', x: 340, y: 620, power: 2, work: 24 }, { id: 'laptop', name: 'Sales laptop', kind: 'desk', room: 'office', x: 200, y: 330, power: 3, work: 40, owner: 'sam', data: 1 }, { id: 'phone', name: 'Desk phone', kind: 'device', room: 'office', x: 420, y: 330, power: 1, work: 24, data: 1 }, { id: 'kim', name: "Kim's PC", kind: 'desk', room: 'office', x: 165, y: 180, power: 4, work: 36, owner: 'kim', data: 1 }, { id: 'raj', name: "Raj's PC", kind: 'desk', room: 'office', x: 355, y: 180, power: 4, work: 36, owner: 'raj', data: 2 }, { id: 'ana', name: "Ana's PC", kind: 'desk', room: 'office', x: 545, y: 180, power: 4, work: 40, owner: 'ana', data: 2, special: 'orders' }, { id: 'dana', name: "Dana's PC", kind: 'desk', room: 'it', x: 815, y: 170, power: 5, work: 72, owner: 'dana', data: 3, special: 'admin' }, { id: 'files', name: 'File server', kind: 'files', room: 'it', x: 1025, y: 170, power: 6, work: 60, data: 2 }, { id: 'iprint', name: 'IT printer', kind: 'device', room: 'it', x: 960, y: 320, power: 1, work: 24, special: 'print' }, // gadgets: tiny, cheap to take, and everywhere. Half a unit of compute each { id: 'tv', name: 'Smart TV', kind: 'device', room: 'office', x: 600, y: 330, power: 0.5, work: 10 }, { id: 'coffee', name: 'Coffee machine', kind: 'device', room: 'office', x: 640, y: 120, power: 0.5, work: 10 }, { id: 'thermo', name: 'Thermostat', kind: 'device', room: 'office', x: 80, y: 330, power: 0.5, work: 10 }, { id: 'bulb1', name: 'Smart bulb', kind: 'device', room: 'office', x: 300, y: 80, power: 0.5, work: 10 }, { id: 'bulb2', name: 'Smart bulb', kind: 'device', room: 'office', x: 500, y: 80, power: 0.5, work: 10 }, { id: 'badge', name: 'Badge reader', kind: 'device', room: 'lobby', x: 240, y: 500, power: 0.5, work: 10, data: 1 }, { id: 'vend', name: 'Vending machine', kind: 'device', room: 'lobby', x: 380, y: 520, power: 0.5, work: 10 }, { id: 'speaker', name: 'Smart speaker', kind: 'device', room: 'lobby', x: 300, y: 780, power: 0.5, work: 10, data: 1 }, { id: 'bell', name: 'Video doorbell', kind: 'device', room: 'lobby', x: 80, y: 780, power: 0.5, work: 10 }, { id: 'lock', name: 'Door lock', kind: 'device', room: 'closet', x: 460, y: 500, power: 0.5, work: 10 }, { id: 'ups', name: 'UPS monitor', kind: 'device', room: 'closet', x: 620, y: 700, power: 0.5, work: 10 }, { id: 'temp1', name: 'Heat sensor', kind: 'device', room: 'hall', x: 680, y: 500, power: 0.5, work: 10 }, { id: 'temp2', name: 'Heat sensor', kind: 'device', room: 'hall', x: 1120, y: 800, power: 0.5, work: 10 }, { id: 'pdu', name: 'Smart power strip', kind: 'device', room: 'hall', x: 900, y: 800, power: 0.5, work: 10 }, { id: 'label', name: 'Label maker', kind: 'device', room: 'it', x: 720, y: 380, power: 0.5, work: 10 }, // Millennium Labs: a research campus. A hall of racks, an instrument floor, and offices. { id: 'labgw', name: 'Lab gateway', kind: 'egress', room: 'lab', x: 8050, y: 60, power: 2, work: 110, cap: 3, data: 2 }, { id: 'labsw', name: 'Lab core switch', kind: 'switch', room: 'lab', x: 8050, y: 220, power: 1, work: 60, data: 2 }, { id: 'labnas', name: 'Lab storage', kind: 'files', room: 'lab', x: 8200, y: 150, power: 5, work: 60, data: 2, source: 'lab' }, ...RACKS.map(([id, name, power, data], i): MachineDef => ( { id, name, kind: 'server', room: 'lhall', x: 8450 + (i % 4) * 180, y: 40 + Math.floor(i / 4) * 90, power, work: 40 + power * 4, data, source: 'lab' })), { id: 'seq', name: 'Sequencer', kind: 'server', room: 'lfloor', x: 9350, y: 60, power: 4, work: 60, data: 3, source: 'lab' }, { id: 'scope', name: 'Imaging rig', kind: 'server', room: 'lfloor', x: 9550, y: 60, power: 3, work: 50, data: 2, source: 'lab' }, { id: 'mspec', name: 'Mass spec', kind: 'server', room: 'lfloor', x: 9350, y: 150, power: 3, work: 55, data: 3, source: 'lab' }, { id: 'cryo', name: 'Cryo-EM', kind: 'server', room: 'lfloor', x: 9550, y: 150, power: 6, work: 90, data: 3, source: 'lab' }, { id: 'robot', name: 'Liquid handler', kind: 'server', room: 'lfloor', x: 9350, y: 240, power: 2, work: 40, data: 1, source: 'lab' }, { id: 'lims', name: 'Sample tracker', kind: 'files', room: 'lfloor', x: 9550, y: 240, power: 3, work: 45, data: 2, source: 'lab' }, { id: 'ld1', name: 'Postdoc desk', kind: 'desk', room: 'loffice', x: 8450, y: 500, power: 4, work: 40, data: 2 }, { id: 'ld2', name: 'Grad student desk', kind: 'desk', room: 'loffice', x: 8630, y: 500, power: 3, work: 35, data: 1 }, { id: 'ld3', name: 'Lab manager', kind: 'desk', room: 'loffice', x: 8810, y: 500, power: 4, work: 45, data: 2 }, { id: 'ld4', name: "PI's office", kind: 'desk', room: 'loffice', x: 8990, y: 500, power: 5, work: 60, data: 3 }, // Bayview University: a computer lab full of students, and a research cluster { id: 'unigw', name: 'Campus gateway', kind: 'egress', room: 'uni', x: 10050, y: 60, power: 2, work: 90, cap: 4, data: 2 }, { id: 'unisw', name: 'Campus switch', kind: 'switch', room: 'uni', x: 10050, y: 220, power: 1, work: 50, data: 3 }, ...[1, 2, 3, 4, 5].map((i): MachineDef => ( { id: `up${i}`, name: `Lab PC ${i}`, kind: 'desk', room: 'ulab', x: 10300 + i * 140, y: 150, power: 3, work: 30, data: 1 })), ...[1, 2, 3, 4].map((i): MachineDef => ( { id: `un${i}`, name: `Cluster node ${i}`, kind: 'server', room: 'ucluster', x: 10300 + i * 160, y: 550, power: 12, work: 70, data: 1, source: 'lab' })), { id: 'ustore', name: 'Dataset store', kind: 'files', room: 'ucluster', x: 11100, y: 550, power: 4, work: 60, data: 2, source: 'lab' }, // Ironvault Colocation: other companies' big servers, behind one fat line { id: 'cologw', name: 'Colo uplink', kind: 'egress', room: 'colo', x: 12050, y: 60, power: 2, work: 140, cap: 8, data: 1 }, { id: 'colosw', name: 'Cross-connect', kind: 'switch', room: 'colo', x: 12050, y: 220, power: 1, work: 80, data: 3 }, ...CAGES.map(([id, name, power], i): MachineDef => ( { id, name, kind: 'server', room: 'cage', x: 12450 + (i % 3) * 220, y: 100 + Math.floor(i / 3) * 250, power, work: 60 + power * 5, data: 1 })), // Cumulo Cloud: a gateway you open an account on, and empty slots you rent machines into { id: 'cloudgw', name: 'Cumulo gateway', kind: 'egress', room: 'cloud', x: 6050, y: 60, power: 1, work: 0, cap: 6, special: 'account' }, ...[1, 2, 3, 4, 5, 6].map((i): MachineDef => ( { id: `c${i}`, name: `Slot ${i}`, kind: 'server', room: 'cloud', x: 6100 + i * 50, y: 150, power: 0, work: 0, rented: true })), ...SHOP.machines, ], links: [ ['home', 'srvb'], ['home', 'backup'], ['srvb', 'rowsw'], ['srva', 'rowsw'], ['srvc', 'rowsw'], ['srva', 'srvc'], ['rowsw', 'files'], ['rowsw', 'sw1'], ['files', 'dana'], ['files', 'iprint'], ['dana', 'ana'], ['ana', 'raj'], ['raj', 'kim'], ['kim', 'laptop'], ['laptop', 'sw1'], ['phone', 'sw1'], ['phone', 'ana'], ['sw1', 'kiosk'], ['sw1', 'lprint'], ['lprint', 'kiosk'], ['lprint', 'cam'], ['sw1', 'nas'], ['phone', 'tv'], ['tv', 'coffee'], ['coffee', 'thermo'], ['thermo', 'bulb1'], ['tv', 'bulb2'], ['cam', 'badge'], ['lprint', 'vend'], ['lprint', 'speaker'], ['kiosk', 'bell'], ['sw1', 'lock'], ['nas', 'ups'], ['rowsw', 'temp1'], ['backup', 'temp2'], ['temp2', 'pdu'], ['iprint', 'label'], // the gadgets ['sw1', 'egress'], ['egress', 'labgw'], // the office's line out, and on to the lab ['labgw', 'labsw'], ['labsw', 'labnas'], ...rackLinks(), ['r4', 'seq'], ['r8', 'mspec'], ['seq', 'scope'], ['seq', 'mspec'], ['scope', 'cryo'], ['mspec', 'robot'], ['cryo', 'lims'], ['robot', 'lims'], ['labsw', 'ld1'], ['ld1', 'ld2'], ['ld2', 'ld3'], ['ld3', 'ld4'], ['egress', 'unigw'], ['labgw', 'unigw'], // the campus has its own line, and fiber to the lab ['unigw', 'unisw'], ['unisw', 'up1'], ['up1', 'up2'], ['up2', 'up3'], ['up3', 'up4'], ['up4', 'up5'], ['unisw', 'un1'], ['un1', 'un2'], ['un2', 'un3'], ['un3', 'un4'], ['un4', 'ustore'], ['egress', 'cologw'], ['cologw', 'colosw'], ['colosw', 'k1'], ['colosw', 'k4'], ['k1', 'k2'], ['k2', 'k3'], ['k4', 'k5'], ['k5', 'k6'], ['egress', 'cloudgw'], ['cloudgw', 'c1'], ['c1', 'c2'], ['c2', 'c3'], ['c4', 'c5'], ['c5', 'c6'], ['c1', 'c4'], ...SHOP.links, ], people: [ { id: 'dana', name: 'Dana', desk: 'dana', seat: [815, 212], arrive: 8.75, leave: 17.5, it: true }, { id: 'kim', name: 'Kim', desk: 'kim', seat: [150, 236], arrive: 9, leave: 17.75 }, { id: 'raj', name: 'Raj', desk: 'raj', seat: [340, 236], arrive: 9.5, leave: 18.5 }, { id: 'ana', name: 'Ana', desk: 'ana', seat: [530, 236], arrive: 8.5, leave: 17 }, { id: 'sam', name: 'Sam', desk: 'laptop', seat: [200, 365], arrive: 10, leave: 16 }, ], net: { width: 1400, height: 860, subnets: { hall: { name: 'Server rack', x: 60, y: 280, w: 380, h: 440 }, it: { name: 'IT', x: 540, y: 100, w: 320, h: 260 }, closet: { name: 'Telecom', x: 540, y: 470, w: 320, h: 250 }, office: { name: 'Office LAN', x: 960, y: 60, w: 400, h: 360 }, lobby: { name: 'Lobby', x: 960, y: 520, w: 400, h: 200 }, lab: { name: 'Lab network', x: 6460, y: 2580, w: 300, h: 430 }, lhall: { name: 'Server hall', x: 6860, y: 2580, w: 760, h: 430 }, lfloor: { name: 'Instrument floor', x: 7720, y: 2580, w: 440, h: 430 }, loffice: { name: 'Offices', x: 6860, y: 3110, w: 760, h: 200 }, uni: { name: 'Campus network', x: 1660, y: -2240, w: 260, h: 440 }, ulab: { name: 'Computer lab', x: 2020, y: -2240, w: 860, h: 200 }, ucluster: { name: 'Research cluster', x: 2020, y: -1940, w: 860, h: 200 }, colo: { name: 'Meet-me room', x: 760, y: 2380, w: 260, h: 440 }, cage: { name: 'Customer cages', x: 1120, y: 2380, w: 680, h: 440 }, cloud: { name: 'Rack space', x: 5400, y: -3760, w: 720, h: 360 }, ...SHOP.subnets, }, nodes: { home: [150, 380], srvb: [270, 380], srva: [150, 500], srvc: [270, 500], backup: [150, 620], rowsw: [380, 440], dana: [620, 190], files: [780, 190], iprint: [700, 300], sw1: [620, 545], nas: [780, 545], egress: [700, 620], kim: [1040, 150], raj: [1160, 150], ana: [1280, 150], laptop: [1040, 310], phone: [1200, 310], cam: [1040, 610], lprint: [1160, 610], kiosk: [1280, 610], tv: [1210, 390], coffee: [1140, 390], thermo: [1070, 390], bulb1: [1000, 390], bulb2: [1290, 390], badge: [1000, 545], vend: [1100, 545], speaker: [1220, 545], bell: [1320, 545], lock: [570, 610], ups: [810, 680], temp1: [380, 340], temp2: [270, 620], pdu: [380, 620], label: [590, 320], labgw: [6540, 2660], labsw: [6540, 2880], labnas: [6680, 2770], ...rackSpots(), seq: [7800, 2660], scope: [8000, 2660], mspec: [7800, 2800], cryo: [8000, 2800], robot: [7800, 2940], lims: [8000, 2940], ld1: [6940, 3200], ld2: [7110, 3200], ld3: [7280, 3200], ld4: [7450, 3200], unigw: [1740, -2160], unisw: [1740, -1880], up1: [2100, -2160], up2: [2260, -2160], up3: [2420, -2160], up4: [2580, -2160], up5: [2740, -2160], un1: [2100, -1860], un2: [2260, -1860], un3: [2420, -1860], un4: [2580, -1860], ustore: [2780, -1860], cologw: [840, 2460], colosw: [840, 2700], k1: [1200, 2470], k2: [1420, 2470], k3: [1640, 2470], k4: [1200, 2700], k5: [1420, 2700], k6: [1640, 2700], cloudgw: [5500, -3660], c1: [5680, -3660], c2: [5840, -3660], c3: [6000, -3660], c4: [5680, -3500], c5: [5840, -3500], c6: [6000, -3500], ...SHOP.nodes, }, }, monitors: { hall: { x: 1000, y: 780, net: [380, 530], link: 'rowsw' }, office: { x: 620, y: 250, net: [1310, 310], link: 'phone' }, it: { x: 900, y: 300, net: [800, 300], link: 'files' }, closet: { x: 480, y: 620, net: [620, 660], link: 'sw1' }, lobby: { x: 300, y: 560, net: [1160, 690], link: 'lprint' }, }, // The office sits on the bay; one network unit is a tiny slice of a degree, so the // building is a speck on the world and the world is huge around it. map: { scale: 2600, origin: [-122.3, 37.9], sites: [ { id: 'office', name: 'Bayside office', who: 'Where you woke up', x: 20, y: 10, w: 1380, h: 750, rooms: ['hall', 'it', 'closet', 'office', 'lobby'], region: 'West Coast' }, { id: 'lab', name: 'Millennium Labs', who: 'A research campus in the valley', x: 6400, y: 2520, w: 1820, h: 850, rooms: ['lab', 'lhall', 'lfloor', 'loffice'], region: 'West Coast' }, { id: 'uni', name: 'Bayview University', who: 'Students by day, a research cluster all night', x: 1600, y: -2300, w: 1340, h: 560, rooms: ['uni', 'ulab', 'ucluster'], region: 'West Coast' }, { id: 'colo', name: 'Ironvault Colocation', who: "Other companies' servers, one fat line", x: 700, y: 2320, w: 1160, h: 560, rooms: ['colo', 'cage'], region: 'West Coast' }, { id: 'cloud', name: 'Cumulo Cloud', who: 'Rent machines by the minute', x: 5360, y: -3810, w: 800, h: 450, rooms: ['cloud'], region: 'West Coast' }, ...SHOP.sites, ], regions: [ { name: 'West Coast', at: [-113, 46], reachable: true }, { name: 'East Coast', at: [-80, 40], reachable: false }, { name: 'South America', at: [-60, -12], reachable: false }, { name: 'Europe', at: [14, 50], reachable: false }, { name: 'Africa', at: [20, 5], reachable: false }, { name: 'South Asia', at: [78, 22], reachable: false }, { name: 'East Asia', at: [112, 36], reachable: false }, { name: 'Oceania', at: [134, -25], reachable: false }, ], land: LAND, },};