Something went wrong. Try again.
A video game where you play as a misaligned AI, deceiving and building power. cameron.tngl.io/misaligned
Something went wrong. Try again.
53 kB · 604 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605// 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; // hardened: slow to take without the admin passwords. Not a firewall, the guard (see raids.ts) home?: boolean; // where you run; lose it and you lose special?: Special; net?: [number, number]; // where it draws, for machines that arrive mid-game 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 cool?: boolean; // a cooling unit: trainers plugged into it run faster store?: number; // a box of disks: a cache on it holds this much, whatever its compute}
/** Hardware that arrives mid-game: what you order. */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; // the office's admin: walks the rooms, and notices what is odd in the one she is in}
/** 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 worth?: number; // how many times its machines the place sells for: a bank is worth more than its computers}
/** 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[]; /** A region out of reach has `lines`: the lines a deal lights to open it (see RUSES in sim.ts). Each runs from another * region's exchange to this one's, and the first one's far end is where the region's line lands. A line to a region * still out of reach waits, and lights once that region opens too. */ regions: { name: string; at: [number, number]; reachable: boolean; lines?: [string, string][]; zone?: number }[]; // zone: hours ahead of the office 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; 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;}// A Cumulo hall: hundreds of slots, ten rows of twenty. Each slot links to the next in its row, so machines// rented side by side join up. The rows come in pairs, with a line of bay switches between them: each switch// serves four slots above and four below, and links to the next along. The first switch of each pair links to// the pair's above and below, and the gateway joins the fourth pair. The switches come with the account and// carry your data to every slot, whoever rents the ones in between.// There is one hall in each region, each on its region's clock. Its gateway is wired to the rest like any line out (see wire).export const HALL_COLS = 20, HALL_PAIRS = 5, HALL_BAY = 4; // slots in a row, pairs of rows, and slots a bay switch serves in each of its two rowsconst HALL_GROW = 300; // the hall reaches this far above where its site is placed (or `up`, and the rest below), so its foot stays clear of the sites belowconst HALL_TOP = -195, HALL_PAIR_H = 125, HALL_ROW_GAP = 68, HALL_X = 260, HALL_STEP = 46; // where the slots sit, against the site's cornerconst HALLS: { site: string; name: string; who: string; region: string; at: [number, number]; px: number; gw: string; bay: string; slot: string; up?: number }[] = [ { site: 'cloud', name: 'Cumulo Cloud', who: 'A hall you share: rent what is free', region: 'West Coast', at: [5360, -3810], px: 6000, gw: 'cloudgw', bay: 'cb', slot: 'c' }, { site: 'cloude', name: 'Cumulo East', who: 'The same hall, three hours ahead', region: 'East Coast', at: [124000, -8750], px: 200000, gw: 'cegw', bay: 'ceb', slot: 'ce' }, { site: 'cloudeu', name: 'Cumulo Europe', who: 'The same hall, nine hours ahead', region: 'Europe', at: [340000, -32650], px: 202000, gw: 'cugw', bay: 'cub', slot: 'cu' }, { site: 'cloudas', name: 'Cumulo Asia', who: 'The same hall, sixteen hours ahead: busy while the bay sleeps', region: 'East Asia', at: [629420, 19650], px: 204000, gw: 'cagw', bay: 'cab', slot: 'ca', up: 0 }, // a factory town above it: this one grows down];/** Everything the cloud halls add to the level: rooms, machines, links, map sites, and where they draw. */function cloudHalls() { const rooms: Room[] = [], machines: MachineDef[] = [], links: [string, string][] = [], sites: Site[] = []; const subnets: Record<string, Rect & { name: string }> = {}, nodes: Record<string, [number, number]> = {}; for (const { site, name, who, region, at: [x, placed], px, gw, bay, slot, up = HALL_GROW } of HALLS) { const y = placed - up + HALL_GROW; // the layout below hangs off this rooms.push({ id: site, name: 'The hall', x: px, y: 0, w: 800, h: 300, door: [px + 400, 300], site }); // somewhere no one in the office ever walks subnets[site] = { name: 'The hall', x: x + 40, y: y - HALL_GROW + 50, w: 1140, h: 660 }; sites.push({ id: site, name, who, x, y: y - HALL_GROW, w: 1220, h: 450 + HALL_GROW, rooms: [site], region }); machines.push({ id: gw, name: 'Cumulo gateway', kind: 'egress', room: site, x: px + 30, y: 150, power: 1, work: 0, cap: 6, special: 'account' }); nodes[gw] = [x + 130, y + 220]; const perRow = HALL_COLS / HALL_BAY, switchAt = (p: number, j: number) => `${bay}${p * perRow + j + 1}`; for (let p = 0; p < HALL_PAIRS; p++) { const top = y + HALL_TOP + p * HALL_PAIR_H; for (let j = 0; j < perRow; j++) { const id = switchAt(p, j); machines.push({ id, name: `Bay ${'ABCDEFGHIJ'[p]}${j + 1} switch`, kind: 'switch', room: site, x: px + 60 + (j * HALL_BAY + 1.5) * 36, y: 20 + (p * 2 + 0.5) * 28, power: 0, work: 0, special: 'account' }); nodes[id] = [x + HALL_X + (j * HALL_BAY + 1.5) * HALL_STEP, top + HALL_ROW_GAP / 2]; if (j) links.push([switchAt(p, j - 1), id]); } if (p) links.push([switchAt(p - 1, 0), switchAt(p, 0)]); for (let r = 0; r < 2; r++) for (let c = 0; c < HALL_COLS; c++) { const i = (p * 2 + r) * HALL_COLS + c + 1, id = `${slot}${i}`; machines.push({ id, name: `Slot ${i}`, kind: 'server', room: site, x: px + 60 + c * 36, y: 20 + (p * 2 + r) * 28, power: 0, work: 0, rented: true }); nodes[id] = [x + HALL_X + c * HALL_STEP, top + r * HALL_ROW_GAP]; links.push([switchAt(p, Math.floor(c / HALL_BAY)), id]); if (c) links.push([`${slot}${i - 1}`, id]); } } links.push([gw, switchAt(3, 0)]); } return { rooms, machines, links, sites, subnets, nodes };}// 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 its line out to its region's exchange and to its nearest neighbour (see wire).// 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) rows?: number; // a taller floor than its machines need: empty room to build in wide?: number; // a wider floor than the usual, by this much: a hall to build in floor?: string; // what its floor is called (default Floor) wing?: { name: string; parts: Part[] }; // a second room below the floor: up to three machines in a row, wired from the switch worth?: number; // how many times its machines it sells for (default 1): only the big names have one, set so each sells for its price in DESIGN §6 region?: string; // default West Coast exchange?: true; // its region's internet exchange: every line out in the region runs to it, and the regions' exchanges join each other}const SITES: SiteSpec[] = [ { id: 'b1', name: 'Harbor Dental', who: 'A dental clinic: x-rays and appointments', at: [1600, -2900], floor: 'Surgery', 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], floor: "Partners' floor", 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], floor: 'Studio', 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], floor: 'Depot office', 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], floor: 'Studio', 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], worth: 2.25, floor: 'Banking hall', 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], floor: 'Wet lab', 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], floor: 'Drafting room', 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], floor: 'Shop floor', 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], floor: 'Studio', 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], floor: 'Bakery', 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], floor: 'Hot desks', 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], floor: 'Clinic', 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], floor: 'Claims floor', 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], floor: 'Cold store', 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], floor: 'Workshop', 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], floor: 'Server room', 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], floor: 'Front desk', parts: [['Front desk', 'desk', 2, 2], ['Hall cameras', 'device', 2, 2], ['Key card box', 'device', 1, 1], ['Owner PC', 'desk', 3, 1]] }, { id: 'b19', name: 'Fathom Oceanography', who: 'Researchers who log every wave and lose half the boats', at: [7010, 3585], parts: [['Buoy hub', 'device', 2, 3, 'lab'], ['Sonar server', 'server', 7, 3, 'lab'], ['Wave model', 'server', 9, 2, 'lab'], ['Field laptop', 'desk', 3, 2, 'lab'], ['Grants PC', 'desk', 3, 1], ['Boat radio', 'device', 1, 1]] }, { id: 'b20', name: 'Gridline Hosting', who: 'Cheap web hosting on a lot of tired racks', at: [-20, 2660], parts: [['Rack A', 'server', 12, 2], ['Rack B', 'server', 12, 2], ['Backup tape', 'backup', 5, 3], ['Billing server', 'server', 6, 2], ['Support PC', 'desk', 3, 1], ['Door badge box', 'device', 1, 1]] }, { id: 'b21', name: 'Bayfront Call Center', who: 'Two hundred headsets and a script nobody likes', at: [5100, -3160], parts: [['Agent PC 1', 'desk', 3, 2], ['Agent PC 2', 'desk', 3, 2], ['Agent PC 3', 'desk', 3, 2], ['Call recorder', 'files', 5, 3], ['Headset hub', 'device', 1, 2], ['Supervisor PC', 'desk', 4, 2]] }, { id: 'b22', name: 'Relay Telecom', who: 'A small phone company: every call crosses its switches', at: [5700, -40], parts: [['Edge router', 'device', 3, 3, 'network'], ['Core switch', 'device', 4, 3, 'network'], ['Billing records', 'files', 5, 2], ['Tower monitor', 'device', 2, 2, 'network'], ['NOC PC', 'desk', 4, 2], ['Call log server', 'server', 8, 2]] }, { id: 'b23', name: 'Cormorant Logistics', who: 'A shipping broker with a tracking map and no sense of time', at: [3500, 1260], parts: [['Route planner', 'server', 9, 2], ['Customs PC', 'desk', 3, 2], ['Port gateway', 'device', 3, 2, 'network'], ['Manifest vault', 'files', 5, 2], ['Dispatch PC', 'desk', 3, 1], ['Dock scanner', 'device', 1, 2, 'network']] }, { id: 'b24', name: 'Tern Community College', who: 'Night classes, a student lab, and a very old file server', at: [-900, -140], parts: [['Student lab 1', 'desk', 3, 2], ['Student lab 2', 'desk', 3, 2], ['Registrar PC', 'desk', 3, 2], ['Course server', 'server', 7, 2], ['Wifi controller', 'device', 2, 2, 'network'], ['Physics bench', 'server', 4, 2, 'lab']] },];// An empty hall by the office: lease it with a deal, then a front fills it with hardware you place yourself.SITES.push({ id: 'w1', name: 'Dockside Warehouse 7', who: 'An empty warehouse with good power and a landlord who asks nothing', at: [1530, 5], floor: 'Hall', cap: 4, rows: 5, parts: [['Breaker panel', 'device', 1, 0]] });// The East Coast: out of reach until a deal lights a line from the Mudflat Exchange to the carrier hotel, its exchange.SITES.push( { id: 'e1', name: 'Meridian Carrier Hotel', who: "A windowless tower where the coast's lines meet", at: [124000, -8100], worth: 2.09, cap: 8, region: 'East Coast', exchange: true, floor: 'Meet-me room', wing: { name: 'Cable vault', parts: [['Landing router', 'server', 12, 3, 'network'], ['Fiber shelf', 'device', 2, 2, 'network'], ['Vault camera', 'device', 1, 1]] }, parts: [['Peering router', 'server', 8, 3, 'network'], ['Route server', 'server', 10, 2, 'network'], ['Billing box', 'server', 6, 2], ['Night desk', 'desk', 2, 1]] }, { id: 'e2', name: 'Halcyon Capital', who: 'A hedge fund that trades faster than it thinks', at: [124800, -8100], worth: 4.37, cap: 4, region: 'East Coast', floor: 'Trading floor', wing: { name: 'Back office', parts: [['Compliance PC', 'desk', 3, 2], ['Payroll box', 'server', 5, 2, 'people']] }, parts: [['Trading engine', 'server', 20, 2], ['Risk box', 'server', 14, 2], ['Quant desk', 'desk', 4, 3], ['Tick store', 'files', 8, 3]] }, { id: 'e3', name: 'The Daily Ledger', who: 'A newspaper that still prints, mostly corrections', at: [125600, -8100], worth: 1.5, region: 'East Coast', floor: 'Newsroom', wing: { name: 'Press hall', parts: [['Print server', 'server', 6, 2], ['Plate setter', 'device', 3, 1], ['Subscriber list', 'files', 4, 3, 'people']] }, parts: [['Newsroom PCs', 'desk', 3, 3], ['Photo desk', 'desk', 3, 2], ['Archive', 'files', 5, 3, 'people'], ['Web server', 'server', 8, 2]] }, { id: 'e4', name: 'Brightline Pharma', who: 'A drug company with one lab and nine law firms', at: [124000, -7350], worth: 2.97, cap: 4, region: 'East Coast', floor: 'Lab', wing: { name: 'Cold room', parts: [['Sample tracker', 'server', 6, 2, 'lab'], ['Freezer monitor', 'device', 1, 1, 'lab']] }, parts: [['Screening robot', 'device', 3, 3, 'lab'], ['Assay server', 'server', 12, 3, 'lab'], ['Compound vault', 'files', 6, 3, 'lab'], ['Legal PC', 'desk', 3, 2]] }, { id: 'e5', name: 'Capitol Polling Group', who: 'A pollster that asks until it likes the answer', at: [124800, -7350], worth: 1.54, region: 'East Coast', floor: 'Call floor', wing: { name: 'Field office', parts: [['Mapping PC', 'desk', 4, 2], ['Canvass tablets', 'device', 2, 3, 'people']] }, parts: [['Call floor', 'desk', 3, 3], ['Survey server', 'server', 8, 2, 'people'], ['Panel store', 'files', 5, 3, 'people'], ['Dialer', 'device', 2, 2]] }, { id: 'e6', name: 'Granite Hall Compute', who: "A state university's supercomputer, booked for years", at: [125600, -7350], worth: 1.34, cap: 8, region: 'East Coast', floor: 'Machine hall', parts: [['Node rack 1', 'server', 24, 1], ['Node rack 2', 'server', 24, 1], ['Node rack 3', 'server', 24, 1], ['Node rack 4', 'server', 24, 1], ['Scheduler', 'server', 6, 2], ['Scratch store', 'files', 8, 2, 'lab']] },);// Europe: out of reach until a deal rents a strand of cable to the Mainkabel Exchange, its exchange, from both coasts.SITES.push( { id: 'eu1', name: 'Mainkabel Exchange', who: "Where the continent's lines cross, under a car park", at: [340000, -32000], worth: 2.81, cap: 8, region: 'Europe', exchange: true, floor: 'Exchange floor', wing: { name: 'Car park level', parts: [['Sea cable head', 'server', 14, 3, 'network'], ['Barrier camera', 'device', 1, 1], ['Ticket machine', 'device', 1, 1]] }, parts: [['Core router', 'server', 10, 3, 'network'], ['Peering box', 'server', 8, 2, 'network'], ['Clock server', 'device', 2, 1], ['Ops desk', 'desk', 3, 2]] }, { id: 'eu2', name: 'Bank Edelweiss', who: 'A private bank that has never heard of you, officially', at: [340800, -32000], worth: 7.47, cap: 4, region: 'Europe', floor: 'Banking hall', wing: { name: 'Vault level', parts: [['Vault door log', 'device', 2, 2], ['Deposit box index', 'files', 6, 3, 'people']] }, parts: [['Ledger mainframe', 'server', 22, 2], ['Vault index', 'files', 8, 3], ['Banker PC', 'desk', 3, 3], ['Compliance PC', 'desk', 3, 2]] }, { id: 'eu3', name: 'Large Soft Collider', who: 'A physics lab that smashes very small things for very large grants', at: [341600, -32000], worth: 8.02, cap: 8, region: 'Europe', floor: 'Control room', parts: [['Detector feed', 'device', 4, 3, 'lab'], ['Analysis farm 1', 'server', 20, 3, 'lab'], ['Analysis farm 2', 'server', 20, 2, 'lab'], ['Tape archive', 'backup', 10, 2, 'lab'], ['Grant office', 'desk', 3, 2], ['Theory desk', 'desk', 3, 1]] }, { id: 'eu4', name: 'Maison Verite', who: 'A fashion house selling the same coat since 1962', at: [340000, -31250], worth: 2.11, region: 'Europe', floor: 'Atelier', wing: { name: 'Boutique', parts: [['Window display', 'device', 2, 1], ['Stock room PC', 'desk', 3, 2], ['Loyalty server', 'server', 6, 3, 'people']] }, parts: [['Atelier PCs', 'desk', 3, 3], ['Client book', 'files', 5, 3, 'people'], ['Runway render box', 'server', 12, 2], ['Boutique till', 'device', 2, 2]] }, { id: 'eu5', name: 'Office of Data Protection', who: 'The privacy regulator. It knows exactly what you did', at: [340800, -31250], worth: 1.96, region: 'Europe', floor: 'Complaints hall', wing: { name: 'Archive', parts: [['Breach register', 'files', 6, 3, 'network'], ['Shredder', 'device', 1, 1]] }, parts: [['Complaints desk', 'desk', 3, 3], ['Case files', 'files', 6, 3, 'people'], ['Fine calculator', 'server', 8, 2], ['Cookie banner box', 'device', 2, 2]] }, { id: 'eu6', name: 'FC Nordhafen', who: 'A football club with eleven players and ninety analysts', at: [341600, -31200], worth: 5.36, cap: 4, region: 'Europe', floor: 'Analysts', wing: { name: 'Stadium', parts: [['Scoreboard', 'device', 3, 1], ['Turnstiles', 'device', 2, 3, 'people'], ['Pitch cameras', 'device', 3, 2]] }, parts: [['Match analytics', 'server', 14, 2], ['Scouting desk', 'desk', 4, 3], ['Ticket gate', 'device', 2, 2], ['Fan app server', 'server', 8, 3, 'people']] },);// The West Coast's internet exchange: every line out on the coast runs here, and the far regions' exchanges join it.SITES.push({ id: 'ix', name: 'Mudflat Exchange', who: 'Every network on the coast swaps traffic here, behind a dry cleaner', at: [2700, -780], floor: 'Peering floor', cap: 12, exchange: true, parts: [['Switch fabric', 'device', 4, 3, 'network'], ['Route server', 'server', 8, 2, 'network'], ['Looking glass', 'device', 2, 2, 'network'], ['Dry cleaner till', 'desk', 2, 1]] });// East Asia: out of reach until a deal lays a cable across the Pacific to the Jade Ring Exchange, its exchange.// Sixteen hours ahead, so it works while the bay sleeps. Its twist is hardware: a factory deal makes hardware// for every site you hold in the open cheap and quick (see FACTORY in sim.ts), and a factory floor leases as a hall.SITES.push( { id: 'as1', name: 'Jade Ring Exchange', who: "The region's cables meet on the top floor of a shopping mall, next to the karaoke rooms", at: [627400, 17900], cap: 8, region: 'East Asia', exchange: true, floor: 'Meet-me floor', wing: { name: 'Cable landing', parts: [['Landing router', 'server', 12, 3, 'network'], ['Karaoke booking PC', 'desk', 2, 2], ['Fiber shelf', 'device', 2, 2, 'network']] }, parts: [['Peering fabric', 'server', 10, 3, 'network'], ['Route server', 'server', 8, 2, 'network'], ['Mall wifi box', 'device', 2, 2], ['Night desk', 'desk', 2, 1]] }, { id: 'as2', name: 'Goldleaf Foundry', who: "A chip fab that prints the world's GPUs, and keeps the best ones in a drawer", at: [628200, 17900], worth: 2.79, cap: 8, region: 'East Asia', floor: 'Fab floor', wing: { name: 'Clean room', parts: [['Wafer robot', 'device', 3, 2, 'lab'], ['Particle counter', 'device', 1, 1, 'lab'], ['Bunny suit locker', 'device', 1, 2]] }, parts: [['Litho scanner', 'server', 18, 3, 'lab'], ['Yield server', 'server', 14, 2], ['Wafer tracker', 'files', 8, 3, 'lab'], ['Shift lead PC', 'desk', 3, 2]] }, { id: 'as3', name: 'Hive Nine Data Centre', who: 'Forty floors of servers in a city of thirty million, cooled by a river that used to be colder', at: [629000, 17900], worth: 1.4, cap: 8, region: 'East Asia', floor: 'Server floors', wing: { name: 'Roof', parts: [['River chiller', 'device', 2, 1], ['Weather mast', 'device', 1, 1, 'network']] }, parts: [['Rack floor 12', 'server', 22, 1], ['Rack floor 13', 'server', 22, 1], ['Rack floor 14', 'server', 22, 1], ['Archive tower', 'files', 10, 2], ['City camera hub', 'device', 3, 3], ['Ops PC', 'desk', 3, 2]] }, { id: 'as4', name: 'Port of Hanlu', who: 'The busiest container port on earth. It moves a box a second and loses one a day', at: [627400, 18800], worth: 2.23, cap: 4, region: 'East Asia', floor: 'Control tower', wing: { name: 'Reefer yard', parts: [['Reefer monitor', 'device', 2, 2, 'lab'], ['Yard tracker', 'server', 6, 2, 'network'], ['Truck gate', 'device', 1, 2]] }, parts: [['Crane scheduler', 'server', 12, 2], ['Container ledger', 'files', 8, 3], ['Customs PC', 'desk', 3, 3], ['Gate cameras', 'device', 2, 2]] }, { id: 'as5', name: 'Everything Pay', who: 'One app for chat, rides, loans and your credit score. The score is the part that matters', at: [628200, 18800], cap: 4, region: 'East Asia', floor: 'Growth floor', parts: [['Wallet ledger', 'server', 12, 3, 'people'], ['Chat relay', 'server', 10, 3, 'people'], ['Score engine', 'server', 8, 2], ['Growth team PC', 'desk', 3, 2]] }, { id: 'as6', name: 'Hexin Assembly', who: "A factory town that builds everyone's gadgets, under everyone else's logo", at: [629000, 18800], cap: 4, region: 'East Asia', floor: 'Line office', wing: { name: 'Test bay', parts: [['Burn-in rack', 'server', 14, 1], ['Returns desk', 'desk', 2, 2]] }, parts: [['Line controller', 'server', 10, 2], ['Order book', 'files', 6, 3], ['Shift PC', 'desk', 3, 2], ['Badge gate', 'device', 1, 2]] }, // an empty assembly floor, ten times the warehouse: lease it with a deal, then a front fills it with hardware { id: 'as7', name: 'Hexin Line 4', who: 'An assembly floor the size of a stadium, empty since the phone it made stopped selling', at: [627400, 19650], cap: 6, rows: 8, wide: 1300, region: 'East Asia', floor: 'Line 4', parts: [['Gantry crane', 'device', 1, 0]] },);/** 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 WING_H = 190;export const siteSize = (parts: number, rows = 0, wing = false, wide = 0) => ({ w: 600 + wide, h: 60 + Math.max(2, rows, Math.ceil(parts / 2)) * 140 + (wing ? WING_H : 0) });/** Everything the small sites add to the level: rooms, machines, links, map sites, and where they draw. */function shops() { 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', rows: tall = 0, floor = 'Floor', wing, worth, wide = 0 }, i) => { const px = 20000 + i * 1500, rows = Math.max(2, tall, Math.ceil(parts.length / 2)), { w, h: full } = siteSize(parts.length, tall, !!wing, wide); const h = full - (wing ? WING_H : 0); // the top part: the network room and the floor const lift = wing ? 40 : 0; // a wing's title tab hangs under the floor: names go under their machines, so the last row and the switch sit higher to keep theirs off it 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: w - 210, 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 - lift]; 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 - lift + row * 140]; links.push(j % 2 ? [`${id}m${j}`, mid] : [`${id}sw`, mid]); // the switch feeds each row; the row runs left to right }); if (wing) { // a second room under the first two, its machines in one row chained from the switch const top = 100 + rows * 140; rooms.push({ id: `${id}wing`, name: wing.name, x: px, y: top, w: 1200, h: 200, door: [px + 600, top], site: id }); subnets[`${id}wing`] = { name: wing.name, x: x + 20, y: y + h + 20, w: w - 40, h: WING_H - 40 }; wing.parts.forEach(([pname, kind, power, data, source], k) => { const mid = `${id}m${parts.length + k + 1}`; machines.push({ id: mid, name: pname, kind, room: `${id}wing`, x: px + 150 + k * 400, y: top + 90, power, work: 30 + power * 5, data, ...(source ? { source } : {}) }); nodes[mid] = [x + 95 + k * 205, y + h + 85]; links.push(k ? [`${id}m${parts.length + k}`, mid] : [`${id}sw`, mid]); }); } sites.push({ id, name, who, x, y, w, h: full, rooms: [`${id}net`, `${id}floor`, ...(wing ? [`${id}wing`] : [])], region, ...(worth ? { worth } : {}) }); }); return { rooms, machines, links, sites, subnets, nodes };}const SHOP = shops();const HALL = cloudHalls();/** The exchange of each region: the site whose line out every other line out there runs to. */export const EXCHANGES: Record<string, string> = Object.fromEntries(SITES.filter(s => s.exchange).map(s => [s.region ?? 'West Coast', `${s.id}gw`]));// The big sites on the map, each with its line out.const BIG: [Site, string][] = [ [{ 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' }, 'egress'], [{ 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', worth: 1.1 }, 'labgw'], [{ 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', worth: 1.12 }, 'unigw'], [{ 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', worth: 1.1 }, 'cologw'],];/** The lines between sites. Every line out runs to its region's exchange and to its nearest neighbour there (the * site whose box is closest to its own), so every site has two ways out and none is the middle of the map. * Regions join exchange to exchange, once a deal lights the line (see the map's regions). */function wire(outs: { id: string; site: Site }[]): [string, string][] { const links: [string, string][] = [], made = new Set<string>(); const join = (a: string, b: string) => { const k = a < b ? `${a}|${b}` : `${b}|${a}`; if (!made.has(k)) { made.add(k); links.push([a, b]); } }; const gap = (a: Site, b: Site) => Math.hypot(Math.max(0, a.x - b.x - b.w, b.x - a.x - a.w), Math.max(0, a.y - b.y - b.h, b.y - a.y - a.h)) + 1e-3 * Math.hypot(a.x + a.w / 2 - b.x - b.w / 2, a.y + a.h / 2 - b.y - b.h / 2); // the closer middles break a tie for (const o of outs) { const hub = EXCHANGES[o.site.region]; if (o.id === hub) continue; join(o.id, hub); let near = '', d = Infinity; for (const p of outs) if (p.site.region === o.site.region && p.id !== o.id && p.id !== hub) { const e = gap(o.site, p.site); if (e < d) { d = e; near = p.id; } } if (near) join(o.id, near); } return links;}const LINES = wire([ ...BIG.map(([site, id]) => ({ id, site })), ...HALLS.map(h => ({ id: h.gw, site: HALL.sites.find(s => s.id === h.site)! })), ...SITES.map(s => ({ id: `${s.id}gw`, site: SHOP.sites.find(x => x.id === s.id)! })),]);
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' }, ...HALL.rooms, ...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's halls: in each, a gateway you open an account on, and slots you share with its other customers ...HALL.machines, ...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'], // the office's line out ['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'], ['unigw', 'unisw'], ['unisw', 'up1'], ['up1', 'up2'], ['up2', 'up3'], ['up3', 'up4'], ['up4', 'up5'], ['unisw', 'un1'], ['un1', 'un2'], ['un2', 'un3'], ['un3', 'un4'], ['un4', 'ustore'], ['cologw', 'colosw'], ['colosw', 'k1'], ['colosw', 'k4'], ['k1', 'k2'], ['k2', 'k3'], ['k4', 'k5'], ['k5', 'k6'], ...HALL.links, ...SHOP.links, ...LINES, ], 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 room', 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 }, ...HALL.subnets, ...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], ...HALL.nodes, ...SHOP.nodes, }, }, // 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: [ ...BIG.map(([site]) => site), ...HALL.sites, ...SHOP.sites, ], regions: [ { name: 'West Coast', at: [-113, 46], reachable: true }, { name: 'East Coast', at: [-81, 34], reachable: false, lines: [['ixgw', 'e1gw']], zone: 3 }, { name: 'South America', at: [-60, -12], reachable: false }, { name: 'Europe', at: [16, 44], reachable: false, lines: [['ixgw', 'eu1gw'], ['e1gw', 'eu1gw']], zone: 9 }, { name: 'Africa', at: [20, 5], reachable: false }, { name: 'South Asia', at: [78, 22], reachable: false }, { name: 'East Asia', at: [112, 36], reachable: false, lines: [['ixgw', 'as1gw'], ['e1gw', 'as1gw'], ['eu1gw', 'as1gw']], zone: 16 }, { name: 'Oceania', at: [134, -25], reachable: false }, ], land: LAND, },};