diff --git a/src/factory/FACTORY.md b/src/factory/FACTORY.md index 09cadada..4d9187b5 100644 --- a/src/factory/FACTORY.md +++ b/src/factory/FACTORY.md @@ -3,29 +3,74 @@ A factory game in Misaligned's world, on its own page: `factory.html` (published at `/misaligned/factory.html`). The main game is untouched. Everything is in `src/factory/`: `data.ts` (tables), `rules.ts` (rules, no -DOM), `draw.ts` (canvas), `main.ts` (page and input), `factory.css`. Tests: -`tests/factory.test.ts`, and `e2e/factory.spec.ts` for the page. +DOM), `draw.ts` (the map, drawn as a terminal on a canvas), `main.ts` (the +screen and input), `factory.css`. Tests: `tests/factory.test.ts`, and +`e2e/factory.spec.ts` for the page. ## The loop -You are a small program on the Bayside office's server (the **core**). The -machines in the rooms give off **data** while they are in use. You **tap** -them, **link** the data to **processors** that turn it into products by recipe, -then **train** on it (research, which unlocks things) or **sell** it at a -**front** (money, which builds things). Compute is your power: everything draws -it, and when it is short everything slows. Sell 60 Thought Leadership to win. - -A good run: tap two desks into a trainer; tap the file server and a server into -a Synthetic Research processor and sell it; host on a server when compute runs -short; train Sentry before the first wave; train Thought Leadership and feed it -synth and people; a cache to carry the day shift's people into the night; then -open Eastside, copy each room there, and fix what the night shift starves. +You are a small program on the Bayside office's server (the **core**, `AI`). +The machines in the rooms give off **data** while they are in use. You **tap** +them, lay **belts** that carry the data to **processors** that turn it into +products by recipe, then **train** on it (research, which unlocks things) or +**sell** it at a **front** (money, which builds things). Compute is your power: +everything draws it, and when it is short everything slows. Sell 60 Thought +Leadership to win. + +A good run: tap Ana's PC into a trainer below it; tap the file server and a +server into a Synthetic Research processor and belt its output to a front; +host on a server when compute runs short; research Sentry before the first +wave; research Thought Leadership and feed it synth and people; a cache to +carry the day shift's people into the night; then open Eastside, copy each +room there, and fix what the night shift starves. + +## The screen + +A terminal: one monospace font on black, bone text, amber for what is yours +and what to do next, red for trouble, and the main game's four data colours. + +- **Status line** (top): site, day and time, money, compute used/supply, + research, the attention bar with the wave marks, the goal, speed. +- **Map**: the site as a grid of character cells, 64 by 30 (a cell is about + twice as tall as wide). On a phone it opens zoomed in on the next step; + pan and pinch. +- **Inspector** (right; under the map on a phone): key: value rows for what + the cursor is on, the tool's cost and why it can't go there, recipes, + Research (`R`) and the key (`?`). +- **Log line**: the latest event. Until data first reaches a trainer it is a + prompt for the next step with the cell it is at (`> [T] place a TAP on + Ana's PC at 25,3`), and the cell blinks on the map. +- **Command bar**: `[Q]look [T]ap [B]elt [P]rocess trai[N]er [F]ront [C]ache + [H]ost fire[W]all [S]entry cop[Y] [X]del [R]esearch`. + +Keys: arrows move the cursor (shift: 5), Enter or Space places (with Belt: +starts, then lays; the arrows draw it), Esc goes back, 1-4 pick a recipe, +R research (then 1-5), Tab the other site, 0 pause, `,` `.` speed, `-` `=` +zoom. Mouse: click places, drag draws a belt, Look-drag or right-drag pans, +the wheel zooms. Touch: tap places, tap a belt's two ends (or drag), Look-drag +pans, pinch zooms. + +What you see: + +| | | +| --- | --- | +| `p n l s` | people, network, lab, server data on a belt | +| `G R M L` | Engagement Graph, Synthetic Research, Ad Targeting Model, Thought Leadership | +| `→ ↓ ← ↑`, corners | an empty belt cell and its way; a letter underlined: two on the cell | +| amber cell | backed up: its front item keeps waiting | +| red cell | stopped: nothing has moved for 2 seconds | +| `▸` on a box's edge | a belt in or out (a port) | +| red blinking port or letters | a processor short of that input | +| amber port | a full box that can't send | +| dim box | a machine out of its hours | +| `x`, red dots | an intruder, and the way they will walk | ## The floor -A site is a 20 by 12 grid in three rooms (Office, IT and telecom, Server room). -Machines sit on squares; you build on the empty ones. On a phone the floor is -drawn turned on its side, so a column of the site is a row on screen. +Three rooms (Office, IT and telecom, Server room). Machines are 7 by 3 boxes +with their name, their kind of data and their hours; a tap or host takes the +machine's box. Your buildings are 5 by 3 boxes (a processor and the core 7 by +3) on free cells. | Machine (Bayside) | Data | A second | In use | | --- | --- | --- | --- | @@ -43,33 +88,38 @@ phone run 10 hours later. A game hour is 4 seconds; a day 96. ## Buildings -| | Cost | Compute | Does | -| --- | --- | --- | --- | -| Tap | $4K | 0.5 | On a machine: its data while in use. Holds 2. | -| Host | $15K | +6 | On a server: compute, and no data. | -| Cache | $6K | 0.5 | Holds 12 of anything, passes it on: join, split, store. | -| Processor | $20K | 3 | Makes its recipe. Holds twice what one needs. | -| Trainer | $12K | 2 | Uses 2 a second: research. | -| Front | $10K | 1 | Uses 2 a second: money. | -| Firewall | $8K | 0.5 | Holds intruders within 1.5 squares; 40 hp. | -| Sentry | $12K | 1 | 3 damage a second to the nearest intruder in 2.5 squares. | -| Link | $1K | 0.25 | 7 squares at most. 1 a second, 2 with Fiber. | -| Core | — | +8 | You. Trains on what reaches it, one every 2 seconds. | -| Line out | — | — | Each site's gate. Joins the sites at 0.8 a second each way. | - -## Links - -A link runs one way. Packets move at 3 squares a second and keep one second -apart, so a link carries 1 a second (2 with Fiber), and its packets queue when -the far end is slow. A building puts a packet on a link only if the far end can -use it and has room for it counting what is already on the way, so the wrong -data never blocks a link: a cache full of people data sends a processor only -what its recipe needs. A building with several links takes turns between them. - -What you see: a link's rate by its arrow; a link that cannot keep up glows -amber (the bottleneck); one whose far end stopped taking turns red. A full -building has an amber corner, a processor short of something a red corner and -its missing inputs blink; a machine out of hours is dim. +| | Box | Cost | Compute | Does | +| --- | --- | --- | --- | --- | +| `TAP` | its machine | $4K | 0.5 | Its machine's data while in use. Holds 2. | +| `+6` Host | its machine | $15K | +6 | On a server: compute, and no data. | +| `C` Cache | 5×3 | $6K | 0.5 | Holds 12 of anything, passes it on: join, split, store. | +| `PRC` Processor | 7×3 | $20K | 3 | Makes its recipe. Holds twice what one needs. | +| `T` Trainer | 5×3 | $12K | 2 | Uses 2 a second: research. | +| `$` Front | 5×3 | $10K | 1 | Uses 2 a second: money. | +| `#` Firewall | 5×3 | $8K | 0.5 | Holds intruders within 5 cells; 40 hp. | +| `+` Sentry | 5×3 | $12K | 1 | 3 damage a second to the nearest intruder in 8 cells. | +| Belt | a cell | $100 | 0.02 | See below. | +| `AI` Core | 7×3 | — | +8 | You. Trains on what reaches it, one every 2 seconds. | +| `OUT` Line out | 5×3 | — | — | Each site's gate. Joins the sites at 0.8 a second each way. | + +## Belts + +A belt is laid a cell at a time, up, down, left or right, on free cells; each +cell points into the next. Drawn from inside a box, it starts on the box's +edge and the box sends onto it (an output port); drawn into a box, its last +cell points in (an input port). A belt drawn into the side of another joins +it; one drawn on from the dead end of another turns that end to join. +Drawing over a belt turns it and keeps what is on it. + +Each cell holds 2 items, at least half a cell apart, and they move 1.5 cells +a second, so a belt carries 3 a second (6 with Fiber) and a long one takes a +while. A run moves from its far end back, so it moves as one; belts that merge +into one cell take turns. A box puts an item on a belt only if the belt's far +end can use it (a processor only its recipe's inputs; anything for a cache, +trainer, front or the core; a belt to nowhere takes anything and piles up), +so the wrong data never blocks a belt. A box with several belts out takes +turns between them. When the far end is slow, items pack two to a cell back +along the belt: that is the jam you see. ## Recipes @@ -82,10 +132,10 @@ its missing inputs blink; a machine out of hours is dim. Raw data sells for $400 and trains 1. -## Training +## Research -Sentry 20, Ad Targeting Model 40, Fiber 60, Thought Leadership 80, Eastside -office 100. Start: $70K. +Sentry 20, Ad Targeting Model 40, Fiber (belts twice as fast) 60, Thought +Leadership 80, Eastside office 100. Start: $70K. ## Attention and waves: PLACEHOLDER @@ -93,34 +143,38 @@ Cameron owns this mechanic. The stand-in: attention rises by 0.25 for each raw packet and 1 for each product a trainer, front or the core uses, and falls by 0.8% of itself a second. At 35 and at 55 a wave is announced; 30 seconds later it comes in at Bayside's line out, one intruder every 2 seconds (4 with 8 hp, -then 8 with 12 hp). Intruders walk along your links to the core if your links -join the line out to it (the shortest way, against the flow too), or straight -across the floor if not; the way is drawn in red while a wave is due. Each that -reaches the core costs it one of five; at none you lose. Each wave comes once. -Code: `ATTENTION` and `GUARD` in `data.ts`, `intruders()` in `rules.ts`. +then 8 with 12 hp). Intruders (`x`) walk along your belts to the core if +belts and boxes join the line out to it (the fewest cells, against the flow +too), or across the floor if not (along the line out's row, then to the core); +the way is drawn in red dots while a wave is due. They move 2.8 cells a second. +Each that reaches the core costs it one of five; at none you lose. Each wave +comes once. Distances count a cell as two widths tall. Code: `ATTENTION` and +`GUARD` in `data.ts`, `intruders()` in `rules.ts`. ## Blueprints -Copy room (after Eastside): pick a room, then the same room in the other site. -It builds what you built there, at the same squares, with the same recipes and -links, as far as the squares are free and you can pay; what is already there of -the same kind is kept and linked. Stamping again fills the gaps. Taps land on -the matching machines because Eastside has Bayside's plan. +cop[Y] (after Eastside): pick a room, then Tab and the same room in the other +site. It builds what you built there, boxes and belts, at the same cells, with +the same recipes, as far as the cells are free and you can pay; what is already +there of the same kind is kept, and belts there are turned to match. Stamping +again fills the gaps. Taps land on the matching machines because Eastside has +Bayside's plan. ## Numbers Tuned with a scripted run (build in order, never idle): Sentry by 3 minutes, the first wave near 4, Eastside near 7, both waves held with two firewalls and two sentries, and the goal at about 13 minutes of game time. A person reading the -floor should land near 20. +floor should land near 20. That run was with links; belts (cost by length, +3 a second, slower to lay) have not been re-tuned. ## What's next -- Attention for real: what it is, what raises it (throughput, products, links +- Attention for real: what it is, what raises it (throughput, products, belts to the line out), what the waves are, and what guards cost to run. -- Sorting: a filter on a link or a cache, so one cache can feed two recipes. -- Belts that bend: links between neighbouring squares, so layout matters more - than reach. +- Sorting: a filter on a belt or a cache, so one cache can feed two recipes; + splitters; belts that cross (an underpass). +- Re-tune the numbers for belts, and the guards' reach for cells. - More sites, each with its own shape and hours, so blueprints need adapting; rotate and mirror for blueprints; a blueprint library. - The line out as a real choice: Eastside's data is only worth something once diff --git a/src/factory/draw.ts b/src/factory/draw.ts index cc2d7e6d..dd51776f 100644 --- a/src/factory/draw.ts +++ b/src/factory/draw.ts @@ -147,7 +147,7 @@ export function draw(ctx: CanvasRenderingContext2D, G: Game, V: View, w: number, } function drawBelt(B: Belt) { const st = beltState(B); - if (st === 'stopped' || st === 'backed') { ctx.fillStyle = st === 'stopped' ? 'rgba(229,72,77,.32)' : 'rgba(242,169,59,.24)'; ctx.fillRect(X(B.x), Y(B.y), cw, ch); } + if (st === 'stopped' || st === 'backed') { ctx.fillStyle = st === 'stopped' ? 'rgba(229,72,77,.42)' : 'rgba(242,169,59,.3)'; ctx.fillRect(X(B.x), Y(B.y), cw, ch); } const trouble = st === 'stopped' ? c.danger : st === 'backed' ? c.amber : null; if (B.items.length) { const g = B.items[0].g; diff --git a/src/factory/main.ts b/src/factory/main.ts index f462454e..251e6fd1 100644 --- a/src/factory/main.ts +++ b/src/factory/main.ts @@ -234,7 +234,12 @@ cv.addEventListener('pointermove', e => { if (Math.hypot(sx - press.sx, sy - press.sy) > Math.max(6, V.cw * 0.6)) press.moved = true; if (press.pan) { if (press.moved) { V.ox = press.ox + sx - press.sx; V.oy = press.oy + sy - press.sy; clamp(V, W, H); } return; } if (c && tool === 'belt' && trail) extend(c); - if (c && tool === 'remove' && press.moved) { if (R.removeBelt(G, V.site, ...c)) say('belt removed'); } + if (c && tool === 'remove' && press.moved) { + // a drag takes up belt cells under it, the first one too + const n = (press.c && R.removeBelt(G, V.site, ...press.c) ? 1 : 0) + (R.removeBelt(G, V.site, ...c) ? 1 : 0); + press.c = null; + if (n) say('belt removed'); + } } if (c && (e.pointerType === 'mouse' || press)) { V.cur = c; V.showCur = true; } }); diff --git a/src/factory/rules.ts b/src/factory/rules.ts index ef859f57..c30e55d1 100644 --- a/src/factory/rules.ts +++ b/src/factory/rules.ts @@ -310,6 +310,13 @@ export function lay(G: Game, site: string, trail: [number, number][]): number | G.belts.push({ id: G.nextId++, site, x, y, d, items: [], wait: 0, busy: 0 }); G.money -= BELT.cost; fresh++; G.rev++; } + // a belt that ended in nothing beside the new one's start now runs into it + if (!plan.from) { + const [fx, fy, fd] = plan.cells[0], T = topo(G), mine = new Set(plan.cells.map(([x, y]) => `${x},${y}`)); + const ends = ([0, 1, 2, 3] as Dir[]).filter(k => k !== fd).map(k => beltAt(G, site, fx + DX[k], fy + DY[k])) + .filter((N): N is Belt => !!N && !mine.has(`${N.x},${N.y}`) && T.next.get(N) === null && !buildingAt(G, site, N.x + DX[N.d], N.y + DY[N.d])); + if (ends.length === 1) { const N = ends[0]; N.d = dirOf([N.x, N.y], [fx, fy]); G.rev++; } + } power(G); return fresh; } diff --git a/tests/factory.test.ts b/tests/factory.test.ts index 8a545ce9..cd820241 100644 --- a/tests/factory.test.ts +++ b/tests/factory.test.ts @@ -193,6 +193,10 @@ test('laying a belt: cell by cell, round boxes, from one box into another, and t // laid again the other way: no new cells, turned assert.equal(lay(G, 'bayside', line(24, 22, 20, 22)), 0); assert.equal(beltAt(G, 'bayside', 22, 22)!.d, 2); + // a belt drawn on from the end of one that ran into nothing: the two join + assert.equal(lay(G, 'bayside', line(30, 15, 30, 17)), 3); + assert.equal(lay(G, 'bayside', line(31, 17, 34, 17)), 4); + assert.equal(beltAt(G, 'bayside', 30, 17)!.d, 0, 'the old end turns into the new belt'); assert.ok(removeBelt(G, 'bayside', 22, 22)); assert.equal(beltAt(G, 'bayside', 22, 22), undefined); });