Waste rock pays: richer granite and basalt grinding, bulk rock orders (t3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 17:39:30 -04:00
co-authored by Claude Opus 5.5
parent 9682cab6d4
commit 06f8f90f19
5 changed files with 25 additions and 5 deletions
+2 -2
View File
@@ -8,7 +8,7 @@ const CLAIM_DEBT = 20000;
// base price per grain (per bar for bars). 0 = not wanted: it drops through for nothing. // base price per grain (per bar for bars). 0 = not wanted: it drops through for nothing.
const PRICE = new Float32Array(NMAT); const PRICE = new Float32Array(NMAT);
for (const [m, v] of [ for (const [m, v] of [
[SAND, 0.1], [LCLAY, 0.2], [LWOOD, 0.3], [ACORN, 0.5], [COAL, 0.5], [RGRAN, 0.2], [RBAS, 0.4], [SAND, 0.1], [LCLAY, 0.2], [LWOOD, 0.3], [ACORN, 0.5], [COAL, 0.5], [RGRAN, 0.3], [RBAS, 0.5],
[CUORE, 1], [FEORE, 1.6], [SNORE, 2], [ZNORE, 2.2], [AGORE, 6], [AUORE, 14], [CUORE, 1], [FEORE, 1.6], [SNORE, 2], [ZNORE, 2.2], [AGORE, 6], [AUORE, 14],
[CHARCOAL, 1.2], [RBRICK, 0.8], [CULLET, 1.2], [BARGL, 14], [RSTONE, 0.02], [GRAVEL, 0.03], [BARSB, 0.6], [PLANK, 2.4], [GREENB, 2], [BARBR, 9], [CHARCOAL, 1.2], [RBRICK, 0.8], [CULLET, 1.2], [BARGL, 14], [RSTONE, 0.02], [GRAVEL, 0.03], [BARSB, 0.6], [PLANK, 2.4], [GREENB, 2], [BARBR, 9],
[SCRCU, 3], [SCRFE, 5], [SCRST, 11], [SCRSN, 3.5], [SCRZN, 3.5], [SCRAG, 18], [SCRAU, 40], [SCRBZ, 8], [SCRBS, 7], [SCRCU, 3], [SCRFE, 5], [SCRST, 11], [SCRSN, 3.5], [SCRZN, 3.5], [SCRAG, 18], [SCRAU, 40], [SCRBZ, 8], [SCRBS, 7],
@@ -73,7 +73,7 @@ const STANDING = ['Local buyers', 'County buyers', 'Regional buyers', 'National
function standing() { return Math.min(STANDING.length - 1, Math.floor(ordersDone / 4)); } function standing() { return Math.min(STANDING.length - 1, Math.floor(ordersDone / 4)); }
// Builders buy the cheap bulk stuff (rubble, gravel, sand, stone blocks, planks) in big lots at a few // Builders buy the cheap bulk stuff (rubble, gravel, sand, stone blocks, planks) in big lots at a few
// times the market price, so waste rock earns a little. [what, smallest lot, largest lot] // times the market price, so waste rock earns a little. [what, smallest lot, largest lot]
const BUILD_GOODS = [[RSTONE, 400, 1500], [GRAVEL, 300, 1000], [SAND, 300, 900], [BARSB, 20, 80], [PLANK, 20, 80], [BARBR, 20, 80]]; const BUILD_GOODS = [[RSTONE, 400, 1500], [RGRAN, 300, 1200], [RBAS, 300, 1200], [GRAVEL, 300, 1000], [SAND, 300, 900], [BARSB, 20, 80], [PLANK, 20, 80], [BARBR, 20, 80]];
function makeBuildersOrder() { function makeBuildersOrder() {
const cands = BUILD_GOODS.filter(([m]) => seen[m] && !orders.some(o => o.m === m)); const cands = BUILD_GOODS.filter(([m]) => seen[m] && !orders.some(o => o.m === m));
if (!cands.length) return null; if (!cands.length) return null;
+2 -2
View File
@@ -5,8 +5,8 @@
// the rest is ground to dust and washed away, so a Grinder halves a pile of waste rock. // the rest is ground to dust and washed away, so a Grinder halves a pile of waste rock.
const REFINE = []; const REFINE = [];
REFINE[RSTONE] = [[0.025, CUORE], [0.02, COAL], [0.015, FEORE], [0.006, SNORE]]; REFINE[RSTONE] = [[0.025, CUORE], [0.02, COAL], [0.015, FEORE], [0.006, SNORE]];
REFINE[RGRAN] = [[0.02, FEORE], [0.012, ZNORE], [0.01, CUORE], [0.002, AGORE]]; REFINE[RGRAN] = [[0.04, FEORE], [0.024, ZNORE], [0.02, CUORE], [0.004, AGORE]];
REFINE[RBAS] = [[0.03, FEORE], [0.01, ZNORE], [0.003, AUORE]]; REFINE[RBAS] = [[0.06, FEORE], [0.02, ZNORE], [0.006, AUORE]];
REFINE[GRAVEL] = [[0.03, FEORE], [0.02, CUORE], [0.01, SNORE], [0.003, ZNORE]]; REFINE[GRAVEL] = [[0.03, FEORE], [0.02, CUORE], [0.01, SNORE], [0.003, ZNORE]];
REFINE[SAND] = [[0.01, SNORE], [0.008, FEORE], [0.0015, AUORE]]; REFINE[SAND] = [[0.01, SNORE], [0.008, FEORE], [0.0015, AUORE]];
REFINE[LDIRT] = [[0.08, LCLAY], [0.01, COAL], [0.008, CUORE], [0.003, ACORN]]; REFINE[LDIRT] = [[0.08, LCLAY], [0.01, COAL], [0.008, CUORE], [0.003, ACORN]];
+1 -1
View File
@@ -69,7 +69,7 @@ function renderMarketTab(body) {
+ `<div><div class="k">Earned in total</div><div class="v">${fmtMoney(earned)}</div></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>`; 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>`; 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="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>`; h += `<p class="label grouphead">Orders · ${esc(STANDING[standing()])}</p>`;
if (orders.length) { 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>` 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>`
+12
View File
@@ -68,6 +68,18 @@ const p2 = run('priceNow(BARCU)');
check('selling 100 copper bars lowers their price, which recovers within half an hour', p1 < p0 * 0.5 && p2 > p0 * 0.9, `${p0} -> ${p1.toFixed(1)} -> ${p2.toFixed(1)}`); check('selling 100 copper bars lowers their price, which recovers within half an hour', p1 < p0 * 0.5 && p2 > p0 * 0.9, `${p0} -> ${p1.toFixed(1)} -> ${p2.toFixed(1)}`);
check('dirt earns nothing, and rubble only a pittance (builders pay a few times that)', run('PRICE[LDIRT]') === 0 && run('PRICE[RSTONE]') > 0 && run('PRICE[RSTONE]') < 0.05); check('dirt earns nothing, and rubble only a pittance (builders pay a few times that)', run('PRICE[LDIRT]') === 0 && run('PRICE[RSTONE]') > 0 && run('PRICE[RSTONE]') < 0.05);
check('refined goods pay more than the raw ore in them', run('PRICE[BARCU] > 13 * PRICE[CUORE] + 13 * PRICE[COAL]') && run('PRICE[BARBZ] > 2 * PRICE[BARCU]')); check('refined goods pay more than the raw ore in them', run('PRICE[BARCU] > 13 * PRICE[CUORE] + 13 * PRICE[COAL]') && run('PRICE[BARBZ] > 2 * PRICE[BARCU]'));
// New bulk rocks stay hidden until discovered; isolate each candidate without random selection.
run('orders = []; ordersDone = 0; seen.fill(0);');
check('builders do not order unseen granite or basalt rubble', run('makeBuildersOrder()') === null);
run('seen[RGRAN] = seen[RBAS] = 1;');
for (const material of ['RGRAN', 'RBAS']) {
run(`orders = [{ m: ${material === 'RGRAN' ? 'RBAS' : 'RGRAN'} }];`);
check(`builders offer bulk ${material === 'RGRAN' ? 'granite' : 'basalt'} rubble after discovery`, run(`(() => {
const o = makeBuildersOrder();
return !!o && o.m === ${material} && o.who === 'Builders' && o.qty >= 300 && o.qty <= 1200 && o.pay > o.qty * PRICE[o.m];
})()`));
}
check('rubble prices rise from stone to granite to basalt', run('PRICE[RSTONE] < PRICE[RGRAN] && PRICE[RGRAN] < PRICE[RBAS]'));
// machines cost money, walls do not // machines cost money, walls do not
flatWorld(run, 240, 160, 100); flatWorld(run, 240, 160, 100);
run('money = 30; bag[RSTONE] = 100; bagTotal = 100; bot.x = 110; bot.y = 70;'); run('money = 30; bag[RSTONE] = 100; bagTotal = 100; bot.x = 110; bot.y = 70;');
+8
View File
@@ -91,6 +91,14 @@ const fed = 400, sand = count(run, run('SAND')), o = ore();
check('the Grinder grinds all the stone rubble it is fed', count(run, run('RSTONE')) === 0 && run('stats.refined') === fed, `${count(run, run('RSTONE'))} left, ${run('stats.refined')} of ${fed} ground`); check('the Grinder grinds all the stone rubble it is fed', count(run, run('RSTONE')) === 0 && run('stats.refined') === fed, `${count(run, run('RSTONE'))} left, ${run('stats.refined')} of ${fed} ground`);
check('about half as much sand comes out', sand > fed * 0.35 && sand < fed * 0.6, `${sand} sand from ${fed} rubble`); check('about half as much sand comes out', sand > fed * 0.35 && sand < fed * 0.6, `${sand} sand from ${fed} rubble`);
check('with the odd grain of common ore', o > 0 && o < fed * 0.15, `${o} ore grains`); check('with the odd grain of common ore', o > 0 && o < fed * 0.15, `${o} ore grains`);
// Old chances are tried in turn, so later ores require every earlier roll to miss.
const oldGraniteChances = [0.02, 0.012, 0.01, 0.002];
const oldGraniteExpected = 2000 * (1 - oldGraniteChances.reduce((miss, chance) => miss * (1 - chance), 1));
grinderRig(); grind('RGRAN', 2000);
const graniteOre = run('stats.refinedOre');
check('all 2000 granite rubble grains are ground', run('stats.refined') === 2000, run('stats.refined'));
check('granite yields at least 1.5 times its old expected ore', graniteOre >= 1.5 * oldGraniteExpected,
`${graniteOre} ore; old expectation ${oldGraniteExpected.toFixed(1)}, minimum ${(1.5 * oldGraniteExpected).toFixed(1)}`);
// gravel, sand and dirt are refined too; ore drops straight through // gravel, sand and dirt are refined too; ore drops straight through
grinderRig(); grind('LDIRT', 60); grinderRig(); grind('LDIRT', 60);
check('dirt goes through the Grinder too', count(run, run('LDIRT')) === 0 && run('stats.refined') === 60); check('dirt goes through the Grinder too', count(run, run('LDIRT')) === 0 && run('stats.refined') === 60);