From 4a8825415d1fffa07b2e593850fcd74163822a76 Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Sat, 24 Oct 2026 17:39:30 -0400 Subject: [PATCH] Waste rock pays: richer granite and basalt grinding, bulk rock orders (t3) Co-Authored-By: Claude Opus 5.5 --- src/sim/economy.js | 4 ++-- src/sim/processors.js | 4 ++-- src/ui/notebook.js | 2 +- test/t_econ.js | 12 ++++++++++++ test/t_pipes.js | 8 ++++++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/sim/economy.js b/src/sim/economy.js index 5df0809..b67d6c2 100644 --- a/src/sim/economy.js +++ b/src/sim/economy.js @@ -8,7 +8,7 @@ const CLAIM_DEBT = 20000; // base price per grain (per bar for bars). 0 = not wanted: it drops through for nothing. const PRICE = new Float32Array(NMAT); 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], [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], @@ -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)); } // 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] -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() { const cands = BUILD_GOODS.filter(([m]) => seen[m] && !orders.some(o => o.m === m)); if (!cands.length) return null; diff --git a/src/sim/processors.js b/src/sim/processors.js index 3560b77..26c4283 100644 --- a/src/sim/processors.js +++ b/src/sim/processors.js @@ -5,8 +5,8 @@ // the rest is ground to dust and washed away, so a Grinder halves a pile of waste rock. const REFINE = []; 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[RBAS] = [[0.03, FEORE], [0.01, ZNORE], [0.003, AUORE]]; +REFINE[RGRAN] = [[0.04, FEORE], [0.024, ZNORE], [0.02, CUORE], [0.004, AGORE]]; +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[SAND] = [[0.01, SNORE], [0.008, FEORE], [0.0015, AUORE]]; REFINE[LDIRT] = [[0.08, LCLAY], [0.01, COAL], [0.008, CUORE], [0.003, ACORN]]; diff --git a/src/ui/notebook.js b/src/ui/notebook.js index 143095b..340957a 100644 --- a/src/ui/notebook.js +++ b/src/ui/notebook.js @@ -69,7 +69,7 @@ function renderMarketTab(body) { + `
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 += `

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.

`; h += `

Orders ยท ${esc(STANDING[standing()])}

`; if (orders.length) { h += '
' + orders.map(o => `
${esc(orderName(o))}${fmtMoney(o.pay)}` diff --git a/test/t_econ.js b/test/t_econ.js index 01dd01b..d7043ae 100644 --- a/test/t_econ.js +++ b/test/t_econ.js @@ -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('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]')); +// 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 flatWorld(run, 240, 160, 100); run('money = 30; bag[RSTONE] = 100; bagTotal = 100; bot.x = 110; bot.y = 70;'); diff --git a/test/t_pipes.js b/test/t_pipes.js index 46e8cc1..4674337 100644 --- a/test/t_pipes.js +++ b/test/t_pipes.js @@ -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('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`); +// 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 grinderRig(); grind('LDIRT', 60); check('dirt goes through the Grinder too', count(run, run('LDIRT')) === 0 && run('stats.refined') === 60);