Files
deepgrain-foundry/src/ui/notebook.js
T
44r0n7andClaude Opus 5.5 95b5eeb99d Vacuum menu on F replaces the pick-up modes (t16)
Pick-up takes everything unless something is marked; F with the Vacuum
(when nothing in reach can be used) or the card chips open a menu with
pick-up and blow toggles per material. The bag rows lose their buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 02:28:56 -04:00

203 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 = `<p class="note">${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.</p><div class="ledger">`;
for (const m of MILESTONES) {
const isDone = !!ledger[m.id], tip = isDone || GAME.tipsOpen.has(m.id);
h += `<div class="ms ${isDone ? 'done' : 'open'}"><span class="mark" aria-hidden="true"></span><span class="h">${esc(m.title)}</span>`;
h += tip ? `<span class="p">${esc(m.hint)}</span>` : `<span class="p"><button class="linkbtn" data-tip="${m.id}">Show tip</button></span>`;
if (m.reward) h += `<span class="rw">${isDone ? '✓ ' : ''}${esc(m.reward)}</span>`;
h += '</div>';
}
h += `</div><p class="note grouphead">Tools: <b>${esc(BIT_TIERS[player.bitTier].name)}</b> (digs hardness ${BIT_TIERS[player.bitTier].hard} and below) · <b>${esc(TANK_TIERS[player.tankTier].name)}</b> (${bagCap().toLocaleString('en-US')} grains)${player.lens ? ' · <b>Thermal lens</b>' : ''}${player.flask ? ' · <b>Flask</b>' : ''}</p>`;
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 += `<tr><td><span class="sw" style="display:inline-block;vertical-align:-2px;margin-right:6px;background:${rgbOf(m)}"></span>${esc(d.name)}</td><td>${ph}</td><td class="num">${d.hard && d.ph === PH_SOLID ? d.hard : '–'}</td><td>${esc(change || '–')}</td><td class="p" style="color:var(--ash)">${esc(d.desc || '')}</td></tr>`;
}
body.innerHTML = `<p class="note">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.` : ''}</p><div class="tablewrap"><table class="mt"><thead><tr><th>Material</th><th>State</th><th>Hard</th><th>What it does</th><th>Notes</th></tr></thead><tbody>${rows}</tbody></table></div>`;
} else if (nbTab === 'Pieces') {
let h = '<p class="note">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.</p><div class="cards">';
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 += `<div class="card"><div class="h"><span>${esc(P.name)}</span><span class="chip ${afford ? 'ok' : ''}">${P.power ? esc(P.power + ' power') : P.id === 'wheel' || P.id === 'engine' ? 'makes power' : size + (size === '1×1' ? ' block' : ' blocks')}</span></div><div class="p">${esc(P.desc)}</div><div class="p">Cost: ${esc(cost)}</div></div>`;
}
body.innerHTML = h + '</div>';
} 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 = `<div class="mkt-sum"><div><div class="k">Money</div><div class="v">${fmtMoney(money)}</div></div>`
+ `<div><div class="k">${debt > 0 ? 'Still owed on the claim' : 'The claim'}</div><div class="v" style="color:${debt > 0 ? 'var(--melt)' : 'var(--ok)'}">${debt > 0 ? fmtMoney(debt) : 'Paid off'}</div></div>`
+ `<div><div class="k">Earned in total</div><div class="v">${fmtMoney(earned)}</div></div></div>`;
if (debt > 0) h += `<div class="mkt-pay"><span class="note" style="margin:0 6px 0 0">Pay toward the claim:</span>${[1000, 5000].map(a => `<button class="btn" data-pay="${a}" ${money >= Math.min(a, debt) ? '' : 'disabled'}>${fmtMoney(Math.min(a, debt))}</button>`).join('')}<button class="btn primary" data-pay="all" ${money > 0 ? '' : 'disabled'}>All I can (${fmtMoney(Math.min(money, debt))})</button></div>`;
else h += `<p class="note">The land is yours. Keep building: the ledger has bigger goals.</p>`;
h += `<p class="note">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.</p>`;
h += `<p class="label grouphead">Orders · ${esc(STANDING[standing()])}</p>`;
if (orders.length) {
h += '<div class="orderlist">' + orders.map(o => `<div class="o"><span class="t"><span class="sw" style="display:inline-block;vertical-align:-2px;margin-right:6px;background:${rgbOf(o.m)}"></span>${esc(orderName(o))}</span><span class="pay">${fmtMoney(o.pay)}</span>`
+ `<span class="d">${o.got} of ${o.qty} delivered · ${o.mult}× market · ${fmtClock(orderLeft(o))} left</span><span></span><span class="pb"><i style="width:${(o.got / o.qty * 100).toFixed(1)}%"></i></span></div>`).join('') + '</div>';
} else h += `<p class="note">No open orders. Buyers post new ones every minute or two, for goods you have found.</p>`;
h += `<p class="note">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.</p>`;
h += `<p class="label grouphead">Prices</p><p class="note">Per grain, or per bar. Selling a lot of one thing lowers its price for a while; it recovers over about half an hour.</p>`;
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 += `<tr><td colspan="3" class="label" style="padding-top:8px">${esc(g)}</td></tr>`;
for (const m of known) {
const f = priceFactor(m), now = priceNow(m);
rows += `<tr><td><span class="sw" style="display:inline-block;vertical-align:-2px;margin-right:6px;background:${rgbOf(m)}"></span>${esc(MD[m].name)}</td><td class="num">${fmtPrice(PRICE[m])}</td><td class="num ${f < 0.97 ? 'dip' : ''}">${fmtPrice(now)}${f < 0.97 ? ` (${Math.round(f * 100)}%)` : ''}</td></tr>`;
}
}
h += rows ? `<div class="tablewrap"><table class="mt"><thead><tr><th>Goods</th><th>Base</th><th>Now</th></tr></thead><tbody>${rows}</tbody></table></div>` : '<p class="note">Find something worth selling first.</p>';
if (unknown) h += `<p class="note">${unknown} more goods to discover.</p>`;
h += `<p class="label grouphead">Sales, minute by minute</p>`;
const recent = saleAcc.total > 0 ? [{ total: saleAcc.total, items: saleAcc.items, now: true }].concat(shipLog) : shipLog;
h += recent.length ? `<div class="shiplog">${recent.map(sl => `<div><span class="what">${sl.now ? 'This minute: ' : ''}${esc(itemsText(sl.items))}</span><b class="num">+${fmtMoney(sl.total)}</b></div>`).join('')}</div>` : '<p class="note">Nothing sold yet.</p>';
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 ? `<span>Claim owed</span><b>${fmtMoney(debt)}</b>` : `<span>Claim</span><b>paid off ✓</b>`;
const h = orders.length ? orders.map(o => {
const left = orderLeft(o);
return `<span class="ord"><span class="sw" style="background:${rgbOf(o.m)}"></span><span class="nm">${o.got}/${esc(orderName(o))}</span><span class="tm${left < 3600 ? ' late' : ''}">${fmtClock(left)}</span><span class="pb"><i style="width:${(o.got / o.qty * 100).toFixed(1)}%"></i></span></span>`;
}).join('') : '<span class="none">No orders yet</span>';
const box = $('w-orders');
if (box.innerHTML !== h) box.innerHTML = h;
}
function renderBlueprintTab(body) {
const lib = libLoad();
let h = `<p class="note">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.</p>`;
h += `<p class="label grouphead">Starter designs</p><p class="note">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.</p><div class="cards">`;
STARTER_DESIGNS.forEach((d, k) => {
const bp = d.bp();
h += `<div class="card"><div class="h"><span>${esc(d.name)}</span><span class="chip">${bp.bw}×${bp.bh} blocks</span></div><div class="p">${esc(d.desc)}</div><div class="p">Build cost: ${esc(costText(bpCost(bp), 1))}</div><div style="display:flex;gap:6px;flex-wrap:wrap"><button class="btn" data-starter="${k}">Place it</button></div></div>`;
});
h += '</div><p class="label grouphead">Your blueprints</p>';
h += `<div class="set"><div><div>Clipboard</div><div class="d">${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'}</div></div><div class="ctl"><button class="btn" id="bp-save" ${GAME.clip ? '' : 'disabled'}>Save to library</button><button class="btn" id="bp-code" ${GAME.clip ? '' : 'disabled'}>Copy code</button></div></div>`;
h += `<div class="set"><div><div>Import a blueprint code</div><div class="d">Paste a code that starts with DGBP2.</div></div><div class="ctl"><button class="btn" id="bp-import">Paste code…</button></div></div>`;
h += `<p class="label grouphead">Library</p>`;
if (!lib.length) h += `<p class="note">No saved blueprints yet.</p>`;
else {
h += '<div class="cards">';
lib.forEach((it, k) => {
const ok = bpValid(it.bp);
h += `<div class="card"><div class="h"><span>${esc(it.name)}</span><span class="chip">${ok ? `${it.bp.bw}×${it.bp.bh}` : 'old format'}</span></div><div class="p">${ok ? esc(costText(bpCost(it.bp), 1) || 'free') : 'Made before the block grid. It cannot be placed.'}</div><div style="display:flex;gap:6px;flex-wrap:wrap">${ok ? `<button class="btn" data-bpload="${k}">Use</button><button class="btn ghost" data-bpcode="${k}">Copy code</button>` : ''}<button class="btn ghost danger" data-bpdel="${k}">Delete</button></div></div>`;
});
h += '</div>';
}
body.innerHTML = h;
const sv = $('bp-save'); if (sv) sv.onclick = () => {
dialog('Name this blueprint', '<input class="text" id="bp-name" maxlength="40" placeholder="Copper smelter">', [
{ 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', '<textarea class="code" id="bp-paste" placeholder="DGBP1:…"></textarea>', [
{ 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', `<p class="note">Select the text and copy it.</p><textarea class="code" id="bp-out" readonly>${esc(code)}</textarea>`, [{ 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 => '<div class="keys">' + rows.map(([a, b]) => `<div><span>${esc(a)}</span><span>${esc(b)}</span></div>`).join('') + '</div>';
return `<p class="label grouphead">Tools</p><div class="cards">${tools.map(([a, b]) => `<div class="card"><div class="h">${esc(a)}</div><div class="p">${esc(b)}</div></div>`).join('')}</div>
<p class="label grouphead">Keyboard and mouse</p>${tbl(kb)}<p class="label grouphead">Gamepad</p>${tbl(pad)}
<p class="label grouphead">First steps</p>
<p class="note">1. Drill down a little. Stone falls as rubble; coal (dark seams), copper ore (green speckles) and clay (reddish) come loose too.<br>
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).<br>
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.<br>
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.<br>
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.<br>
Screw tubes carry grains and whole bars: feed bars in from a belt, turn corners and unload at the mouth.<br>
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.<br>
7. From there: deeper ores (tin, zinc, silver, gold), alloys, fans for hotter fires, iron and steel.</p>
`;
}