// The notebook (N): milestones, market, wallet, blueprints, controls. // ---------- Notebook ---------- let nbTab = 'Ledger'; function openNotebook(tab) { if (tab) nbTab = tab; renderNotebook(); openModal('m-notebook'); } function renderNotebook() { const tabs = $('nb-tabs'); tabs.innerHTML = ''; for (const name of ['Ledger', 'Market', 'Materials', 'Pieces', 'Blueprints', 'Controls']) { const b = el('button', { role: 'tab', class: name === nbTab ? 'on' : '', 'aria-selected': name === nbTab }, name); b.onclick = () => { playSfx('ui'); nbTab = name; renderNotebook(); }; tabs.appendChild(b); } const body = $('nb-body'); if (nbTab === 'Ledger') { const done = MILESTONES.filter(m => ledger[m.id]).length; let h = `

${done} of ${MILESTONES.length} reached. There is no set order. Tips stay hidden until you ask for one. Upgrades come from what you make: your bit is as hard as the metal you cast it from.

`; for (const m of MILESTONES) { const isDone = !!ledger[m.id], tip = isDone || GAME.tipsOpen.has(m.id); h += `
${esc(m.title)}`; h += tip ? `${esc(m.hint)}` : ``; if (m.reward) h += `${isDone ? '✓ ' : ''}${esc(m.reward)}`; h += '
'; } h += `

Tools: ${esc(BIT_TIERS[player.bitTier].name)} (digs hardness ${BIT_TIERS[player.bitTier].hard} and below) · ${esc(TANK_TIERS[player.tankTier].name)} (${bagCap().toLocaleString('en-US')} grains)${player.lens ? ' · Thermal lens' : ''}${player.flask ? ' · Flask' : ''}

`; body.innerHTML = h; body.querySelectorAll('[data-tip]').forEach(b => b.onclick = () => { playSfx('ui'); GAME.tipsOpen.add(b.dataset.tip); renderNotebook(); }); } else if (nbTab === 'Market') { renderMarketTab(body); } else if (nbTab === 'Materials') { let rows = '', unknown = 0; const list = [DIRT, LDIRT, STONE, RSTONE, CLAY, LCLAY, WOOD, LWOOD, ACORN, CHARCOAL, SNOW, ICE, COALV, COAL, CUV, CUORE, FEV, FEORE, SAND, GRAVEL, GRANITE, RGRAN, BASALT, RBAS, BRICK, RBRICK, GLASS, CULLET, COPPER, SCRCU, BARCU, IRON, SCRFE, BARFE, STEEL, SCRST, BARST, ASH, SLAG, MSLAG, WATER, STEAM, MAGMA, MCU, MFE, MST, MGL, FIRE, SMOKE, SNV, SNORE, TIN, MSN, SCRSN, BARSN, ZNV, ZNORE, ZINC, MZN, SCRZN, BARZN, AGV, AGORE, SILVER, MAG, SCRAG, BARAG, AUV, AUORE, GOLD, MAU, SCRAU, BARAU, BRONZE, MBZ, SCRBZ, BARBZ, BRASS, MBS, SCRBS, BARBS]; for (const m of list) { if (!seen[m]) { unknown++; continue; } const d = MD[m]; const ph = ['', 'Solid', 'Loose', 'Liquid', 'Gas', 'Object'][d.ph]; const change = reactionsOf(m, true).map((t, k) => k ? t : t[0].toUpperCase() + t.slice(1)).join(' · '); rows += `${esc(d.name)}${ph}${d.hard && d.ph === PH_SOLID ? d.hard : '–'}${esc(change || '–')}${esc(d.desc || '')}`; } body.innerHTML = `

What you have found so far. Hardness is what your drill bit has to match. Solid rock breaks into loose rubble that you vacuum up and build with.${unknown ? ` ${unknown} more to discover.` : ''}

${rows}
MaterialStateHardWhat it doesNotes
`; } else if (nbTab === 'Pieces') { let h = '

Everything you build snaps to a grid of 4×4-grain blocks. Pieces only move, hold, blow or sense material; every change to the material itself comes from heat. Select pieces with the Build tool (3). Hover a tile for its cost, full description and placement help. Your bot flies straight through anything you build.

'; for (const P of PIECES) { if (P.hidden) continue; const cost = P.kind === 'mold' ? `${MOLDS[P.mold].walls} grains of wall material (R cycles, F opens the menu)` : P.code === BP_WALL ? '16 grains of rubble, lumps or chunks, or 2 bars' : costText(P.cost, 1); const afford = P.kind === 'mold' || P.code === BP_WALL ? true : canAfford(P.cost, 1); const size = P.kind === 'machine' ? `${P.bw}×${P.bh}` : P.kind === 'mold' ? `${MOLDS[P.mold].bw}×${MOLDS[P.mold].bh}` : '1×1'; h += `
${esc(P.name)}${P.power ? esc(P.power + ' power') : P.id === 'wheel' || P.id === 'engine' ? 'makes power' : size + (size === '1×1' ? ' block' : ' blocks')}
${esc(P.desc)}
Cost: ${esc(cost)}
`; } body.innerHTML = h + '
'; } else if (nbTab === 'Blueprints') { renderBlueprintTab(body); } else { body.innerHTML = controlsHtml(); } } function fmtClock(ticks) { const s = Math.max(0, Math.ceil(ticks / 60)); return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`; } function orderName(o) { return `${o.who ? o.who + ': ' : ''}${o.qty.toLocaleString('en-US')} ${MD[o.m].name.toLowerCase()}${PH[o.m] === PH_ITEM && o.qty !== 1 ? 's' : ''}`; } function orderLeft(o) { return Math.max(0, o.due - tick); } const MARKET_GROUPS = [ ['Raw', [COAL, LWOOD, ACORN, SAND, LCLAY, RGRAN, RBAS, CUORE, FEORE, SNORE, ZNORE]], ['Bulk (builders)', [RSTONE, GRAVEL, BARSB]], ['Refined', [CHARCOAL, RBRICK, CULLET, BARGL, SCRCU, SCRFE, SCRST, SCRSN, SCRZN, BARCU, BARSN, BARZN, BARFE, BARST]], ['Alloys', [SCRBZ, SCRBS, BARBZ, BARBS]], ['Precious', [AGORE, AUORE, SCRAG, SCRAU, BARAG, BARAU]], ]; function renderMarketTab(body) { let h = `
Money
${fmtMoney(money)}
` + `
${debt > 0 ? 'Still owed on the claim' : 'The claim'}
${debt > 0 ? fmtMoney(debt) : 'Paid off'}
` + `
Earned in total
${fmtMoney(earned)}
`; if (debt > 0) h += `
Pay toward the claim:${[1000, 5000].map(a => ``).join('')}
`; else h += `

The land is yours. Keep building: the ledger has bigger goals.

`; h += `

Anything that drops through the trade dock's grate is sold on the spot, once it is cool enough to handle. Junk like dirt, rubble, ash and slag goes for nothing. Build walls around the dock to funnel more in.

`; h += `

Orders · ${esc(STANDING[standing()])}

`; if (orders.length) { h += '
' + orders.map(o => `
${esc(orderName(o))}${fmtMoney(o.pay)}` + `${o.got} of ${o.qty} delivered · ${o.mult}× market · ${fmtClock(orderLeft(o))} left
`).join('') + '
'; } else h += `

No open orders. Buyers post new ones every minute or two, for goods you have found.

`; h += `

Goods dropped into the dock fill a matching order first. A missed deadline costs nothing: what you delivered is paid at market price. ${ordersDone} filled so far; bigger buyers come with more.

`; h += `

Prices

Per grain, or per bar. Selling a lot of one thing lowers its price for a while; it recovers over about half an hour.

`; let rows = '', unknown = 0; for (const [g, list] of MARKET_GROUPS) { const known = list.filter(m => seen[m]); unknown += list.length - known.length; if (!known.length) continue; rows += `${esc(g)}`; for (const m of known) { const f = priceFactor(m), now = priceNow(m); rows += `${esc(MD[m].name)}${fmtPrice(PRICE[m])}${fmtPrice(now)}${f < 0.97 ? ` (${Math.round(f * 100)}%)` : ''}`; } } h += rows ? `
${rows}
GoodsBaseNow
` : '

Find something worth selling first.

'; if (unknown) h += `

${unknown} more goods to discover.

`; h += `

Sales, minute by minute

`; const recent = saleAcc.total > 0 ? [{ total: saleAcc.total, items: saleAcc.items, now: true }].concat(shipLog) : shipLog; h += recent.length ? `
${recent.map(sl => `
${sl.now ? 'This minute: ' : ''}${esc(itemsText(sl.items))}+${fmtMoney(sl.total)}
`).join('')}
` : '

Nothing sold yet.

'; body.innerHTML = h; body.querySelectorAll('[data-pay]').forEach(b => b.onclick = () => { const v = b.dataset.pay === 'all' ? money : +b.dataset.pay; const paid = payClaim(v); if (paid > 0) { playSfx('sale'); toast(`Paid ${fmtMoney(paid)} toward the claim.`); } renderNotebook(); hudDirty = true; }); } function fmtPrice(v) { return v >= 10 ? '$' + Math.round(v) : '$' + v.toFixed(2); } function itemsText(items) { const e = Object.entries(items).map(([m, n]) => [+m, n]).sort((a, b) => b[1] * PRICE[b[0]] - a[1] * PRICE[a[0]]); const parts = e.slice(0, 3).map(([m, n]) => PH[m] === PH_ITEM ? `${n} ${MD[m].name.toLowerCase()}${n === 1 ? '' : 's'}` : `${n} ${MD[m].name.toLowerCase()}`); if (e.length > 3) parts.push(`${e.length - 3} more`); return parts.join(', '); } function renderWallet() { $('w-money').textContent = fmtMoney(money); const c = $('w-claim'); c.className = 'line' + (debt <= 0 ? ' paid' : ''); c.innerHTML = debt > 0 ? `Claim owed${fmtMoney(debt)}` : `Claimpaid off ✓`; const h = orders.length ? orders.map(o => { const left = orderLeft(o); return `${o.got}/${esc(orderName(o))}${fmtClock(left)}`; }).join('') : 'No orders yet'; const box = $('w-orders'); if (box.innerHTML !== h) box.innerHTML = h; } function renderBlueprintTab(body) { const lib = libLoad(); let h = `

Select the Blueprint tool (5), drag over something you built to copy it, then click to place a ghost. Your bot builds ghosts from the materials in your bag when it is within reach. Right-click clears ghosts.

`; h += `

Starter designs

Optional. Tested builds to learn from or copy; place one as a ghost and your bot builds it from your bag. Clear the space first: ghosts wait for open air.

`; STARTER_DESIGNS.forEach((d, k) => { const bp = d.bp(); h += `
${esc(d.name)}${bp.bw}×${bp.bh} blocks
${esc(d.desc)}
Build cost: ${esc(costText(bpCost(bp), 1))}
`; }); h += '

Your blueprints

'; h += `
Clipboard
${GAME.clip ? `${GAME.clip.bw} × ${GAME.clip.bh} blocks, ${GAME.clip.blocks.length} pieces${GAME.clip.machines.length ? `, ${GAME.clip.machines.length} machines` : ''}${GAME.clip.molds.length ? `, ${GAME.clip.molds.length} molds` : ''}. Needs ${esc(costText(bpCost(GAME.clip), 1) || 'nothing')}` : 'Empty'}
`; h += `
Import a blueprint code
Paste a code that starts with DGBP2.
`; h += `

Library

`; if (!lib.length) h += `

No saved blueprints yet.

`; else { h += '
'; lib.forEach((it, k) => { const ok = bpValid(it.bp); h += `
${esc(it.name)}${ok ? `${it.bp.bw}×${it.bp.bh}` : 'old format'}
${ok ? esc(costText(bpCost(it.bp), 1) || 'free') : 'Made before the block grid. It cannot be placed.'}
${ok ? `` : ''}
`; }); h += '
'; } body.innerHTML = h; const sv = $('bp-save'); if (sv) sv.onclick = () => { dialog('Name this blueprint', '', [ { label: 'Cancel', cls: 'ghost' }, { label: 'Save', cls: 'primary', fn: () => { const n = ($('bp-name').value || 'Untitled').trim(); const l = libLoad(); l.push({ name: n, bp: GAME.clip }); if (libSave(l)) toast(`Saved “${n}”.`); renderNotebook(); } }, ]); setTimeout(() => $('bp-name') && $('bp-name').focus(), 30); }; const cc = $('bp-code'); if (cc) cc.onclick = () => copyCode(bpEncode(GAME.clip)); $('bp-import').onclick = () => { dialog('Import blueprint', '', [ { label: 'Cancel', cls: 'ghost' }, { label: 'Import', cls: 'primary', fn: () => { try { GAME.clip = bpDecode($('bp-paste').value); GAME.paste = true; hudDirty = true; toast('Blueprint ready. Click to place it.'); closeAllModals(); } catch (e) { toast(e.message); return true; } } }, ]); }; body.querySelectorAll('[data-bpload]').forEach(b => b.onclick = () => { GAME.clip = libLoad()[+b.dataset.bpload].bp; GAME.paste = true; hudDirty = true; closeAllModals(); toast('Blueprint ready. Click to place it.'); }); body.querySelectorAll('[data-starter]').forEach(b => b.onclick = () => { GAME.clip = STARTER_DESIGNS[+b.dataset.starter].bp(); GAME.paste = true; hudDirty = true; closeAllModals(); toast('Click to place it. R flips it.'); }); body.querySelectorAll('[data-bpcode]').forEach(b => b.onclick = () => copyCode(bpEncode(libLoad()[+b.dataset.bpcode].bp))); body.querySelectorAll('[data-bpdel]').forEach(b => b.onclick = () => { const l = libLoad(); l.splice(+b.dataset.bpdel, 1); libSave(l); renderNotebook(); }); } function copyCode(code) { const done = () => toast('Blueprint code copied.'); const fallback = () => { dialog('Blueprint code', `

Select the text and copy it.

`, [{ label: 'Done', cls: 'primary' }]); setTimeout(() => { const t = $('bp-out'); if (t) { t.focus(); t.select(); } }, 30); }; try { navigator.clipboard.writeText(code).then(done, fallback); } catch (e) { fallback(); } } function controlsHtml() { const kb = [ ['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'], ['Tools', '1 – 4'], ['Cycle what you blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall or mold material menu (Build)', 'F'], ['Mark/unmark what is under the cursor (Vacuum)', 'R'], ['Vacuum menu: what to pick up and blow', 'F (with the Vacuum, away from anything to use)'], ['Nozzle or brush size', '[ ] or Shift + wheel'], ['Replace what is built (Build)', 'Hold G'], ['Copy a box of what you built', 'Hold C, drag'], ['Cut a box (refunded, paste it back)', 'Hold X, drag'], ['Paste again', 'V'], ['Undo building', 'Ctrl + Z'], ['Blueprint library', 'B'], ['Zoom', 'Mouse wheel'], ['Interact (gates, molds, fans, filters)', 'F'], ['Thermal lens', 'H'], ['Info overlay: filters, belt and tube directions', 'I'], ['Bot lamp', 'L'], ['Pick the piece under the cursor (with its direction and settings)', 'Middle mouse'], ['Notebook', 'N'], ['Clear all blueprint ghosts', 'Delete'], ['Save', 'Ctrl + S'], ['Pause · menu', 'Esc'], ['Mute', 'M'], ]; const pad = [ ['Fly', 'Left stick'], ['Boost', 'Left stick pushed all the way'], ['Aim', 'Right stick'], ['Use tool · suck', 'RT'], ['Second action · blow', 'LT'], ['Tools', 'LB / RB'], ['Cycle what you blow · piece', 'D-pad ← →'], ['Brush size', 'D-pad ↑ ↓'], ['Interact', 'A'], ['Variant · mark material', 'X'], ['Thermal lens', 'Y'], ['Cancel', 'B'], ['Zoom', 'R3'], ['Lamp', 'L3'], ['Notebook', 'View / Back'], ['Menu', 'Menu / Start'], ]; const tools = [ ['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'], ['2 Vacuum', 'Left click picks up everything, or only marked materials once you mark some. F opens the vacuum menu to mark what to pick up (⬇) and blow (⬆). R over a grain marks it. Hold Shift while not flying to take everything. Right click blows the ⬆ materials as an even mix, bars one at a time. Click a bag name or use Q/E to blow only one; it stays chosen when empty.'], ['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, tubes, filters, grates, gates, pipes, pumps and outlets, machines (fans, the Refiner) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. Right click takes a piece back. Your bot flies through anything you build.'], ['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'], ['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'], ]; const tbl = rows => '
' + rows.map(([a, b]) => `
${esc(a)}${esc(b)}
`).join('') + '
'; return `

Tools

${tools.map(([a, b]) => `
${esc(a)}
${esc(b)}
`).join('')}

Keyboard and mouse

${tbl(kb)}

Gamepad

${tbl(pad)}

First steps

1. Drill down a little. Stone falls as rubble; coal (dark seams), copper ore (green speckles) and clay (reddish) come loose too.
2. With the Vacuum, left click to pick up loose materials. To take only coal, copper ore, rubble or clay, mark what you want in the vacuum menu (F).
3. Build (3) a chamber of stone wall blocks with a one-block vent in the roof, a floor of clay Grates, and an Ingot mold right under the grates. The notebook's Blueprints tab has this as a starter design.
4. In the vacuum menu (F), mark coal and copper ore with ⬆ to blow both. Fly inside (the bot passes through what you build) and right click to blow an even mix onto the grates: about 50 of each. (Ore first and coal on top works too; coal buried under ore will not light.) Light a coal grain on top with the Igniter.
5. When the ingot sets, bars drop out of the bottom of the mold onto whatever is under it. Leave one open block under it: a belt below that carries them off, or vacuum them up. Leftover metal stays for the next firing. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.
Screw tubes carry grains and whole bars: feed bars in from a belt, turn corners and unload at the mouth.
6. Blow the bars into the trade dock: they sell the moment they drop through its grate, and bars pay far more than ore. Watch the Market page for orders that pay double. Money buys belts, screw tubes and fans; pay down the claim there too.
7. From there: deeper ores (tin, zinc, silver, gold), alloys, fans for hotter fires, iron and steel.

`; }