Refactor: merge duplicated rules, split the largest files by topic (t21)

- One helper each for wall and mold refunds, tube edges and lanes, pipe text, piece dimensions,
  font lookup, reach circles and surface averaging.
- previews, smooth, notebook, materials, worldgen and selection split into topic files
  (preview-drill, preview-selection, smooth-shaders, notebook-market, notebook-blueprints,
  material-tables, worldgen-helpers, selection-pickers, selection-panels).
- Every suite's output identical apart from timings; seeds make the same worlds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 22:35:12 -04:00
co-authored by Claude Opus 5.5
parent f57bdd6084
commit 80420c3453
28 changed files with 1036 additions and 953 deletions
+68
View File
@@ -0,0 +1,68 @@
// Notebook market: prices, orders, claim payments and wallet.
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, PLANK, GREENB, BARBR]],
['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, ash and slag goes for nothing. Rock you dig still sells, builders order it by the bagful, and a Grinder finds ore in it. 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;
}