Skip to content

Commit 15d237b

Browse files
bod09claude
andcommitted
Fix gem processing order: flat bonuses before percentage multipliers
The solver now chains gem machines in correct order: 1. Single-input processors (Gem Cutter: 1.4x multiply) 2. Polisher (+$10 flat) — before % machines so they amplify it 3. Same-type combines (Prismatic: 1.15x) 4. QA (1.2x) — last for maximum effect Previously Polisher was applied after all processors, losing value. Diamond gem: $5.1K → $5.8K per gem from correct ordering. Tooltip path updated to match solver order. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 80c82d3 commit 15d237b

2 files changed

Lines changed: 61 additions & 44 deletions

File tree

js/chain-solver.js

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -578,36 +578,47 @@ class ChainSolver {
578578
if (m.gemType) {
579579
const gemData = typeof GEMS !== 'undefined' ? GEMS.find(g => g.name === m.gemType) : null;
580580
let gemVal = gemData?.value || 0;
581-
// Find best gem processing
582-
let bestProcessed = gemVal;
581+
582+
// Chain gem processing in correct order:
583+
// 1. Single-input processors (gem_cutter: multiply 1.4x)
584+
// 2. Flat modifiers (polisher: +$10) — before percentages to maximize
585+
// 3. Same-type combines (prismatic: gem+gem combine 1.15x)
586+
// 4. QA (percent 1.2x) — last for max effect
587+
588+
// Step 1: Find and apply single-input gem processors (multiply/flat)
589+
let processed = gemVal;
590+
const skipEff = new Set(["chance", "transport", "split", "overflow", "filter", "gate", "duplicate", "preserve", "set", "combine"]);
583591
for (const [procId, procM] of this.registry.machines) {
584592
if (!this.registry.isAvailable(procId, this.config)) continue;
585-
if (!procM.inputs) continue;
586-
const acceptsGem = procM.inputs.some(inp =>
587-
inp === "gem" || inp.split("|").includes("gem") || inp === "any"
588-
);
589-
if (!acceptsGem) continue;
590-
const skipEff = new Set(["chance", "transport", "split", "overflow", "filter", "gate", "duplicate", "preserve", "set"]);
593+
if (!procM.inputs || procM.inputs.length !== 1) continue;
591594
if (skipEff.has(procM.effect)) continue;
592-
if (procM.inputs.length > 1) {
593-
const allGem = procM.inputs.every(inp => inp === "gem" || inp.split("|").includes("gem"));
594-
if (!allGem) continue;
595-
}
596-
let processed = gemVal;
597-
if (procM.effect === "flat") processed += procM.value;
598-
else if (procM.effect === "multiply") processed *= procM.value;
599-
else if (procM.effect === "combine") {
600-
processed = gemVal * procM.inputs.length * procM.value / procM.inputs.length;
601-
}
602-
if (processed > bestProcessed) bestProcessed = processed;
595+
const acceptsGem = procM.inputs.some(inp => inp === "gem" || inp.split("|").includes("gem"));
596+
if (!acceptsGem) continue;
597+
if (procM.effect === "multiply") processed *= procM.value;
598+
else if (procM.effect === "flat") processed += procM.value;
603599
}
604-
// Stack QA + polisher if safe
605-
if (this._oreChainTags?.has("Polished")) bestProcessed += 10;
600+
601+
// Step 2: Polisher (+$10 flat) — before combines/QA so they amplify it
602+
if (this._oreChainTags?.has("Polished")) processed += 10;
603+
604+
// Step 3: Same-type combines (prismatic: gem+gem → 1.15x combined)
605+
for (const [combId, combM] of this.registry.machines) {
606+
if (!this.registry.isAvailable(combId, this.config)) continue;
607+
if (combM.effect !== "combine") continue;
608+
const allGem = (combM.inputs || []).every(inp => inp === "gem" || inp.split("|").includes("gem"));
609+
if (!allGem) continue;
610+
const inputCount = (combM.inputs || []).length;
611+
// Combined value per gem input: (N gems × value) × multiplier / N
612+
processed = processed * inputCount * (combM.value || 1) / inputCount;
613+
}
614+
615+
// Step 4: QA (1.2x percent) — last
606616
const qa = this.registry.get("quality_assurance");
607617
if (qa && this.registry.isAvailable("quality_assurance", this.config)) {
608-
bestProcessed *= (1 + qa.value);
618+
processed *= (1 + qa.value);
609619
}
610-
byproductValue = bestProcessed * ds;
620+
621+
byproductValue = processed * ds;
611622
} else if (m.byproducts?.[0]?.type) {
612623
const bpResult = this._solve(m.byproducts[0].type, baseOreValue);
613624
byproductValue = bpResult?.value || 0;

js/graph-builder.js

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -390,34 +390,40 @@ class FlowGraphBuilder {
390390
const gemData = typeof GEMS !== 'undefined' ? GEMS.find(g => g.name === gemType) : null;
391391
nodeValue = gemData?.value || step.byproductValue || 0;
392392
secondaryValue = step.value || 0;
393-
// Build processing path for tooltip
393+
// Build processing path for tooltip — must match solver order:
394+
// 1. Single-input processors (Gem Cutter)
395+
// 2. Polisher (+flat, before % machines)
396+
// 3. Same-type combines (Prismatic)
397+
// 4. QA (% last)
394398
const path = [gemType + ' Gem'];
395-
const gemProcessors = [];
399+
const skipEff = new Set(['chance','transport','split','overflow','filter','gate','duplicate','preserve','set','combine']);
400+
401+
// Step 1: single-input gem processors
396402
for (const [gpId, gpM] of registry.machines) {
397403
if (!registry.isAvailable(gpId, config)) continue;
398-
const acceptsGem = (gpM.inputs || []).some(inp => inp === 'gem' || inp.split('|').includes('gem'));
404+
if (!gpM.inputs || gpM.inputs.length !== 1) continue;
405+
if (skipEff.has(gpM.effect)) continue;
406+
const acceptsGem = gpM.inputs.some(inp => inp === 'gem' || inp.split('|').includes('gem'));
399407
if (!acceptsGem) continue;
400-
const skip = new Set(['chance','transport','split','overflow','filter','gate','duplicate','preserve','set']);
401-
if (skip.has(gpM.effect)) continue;
402-
if (gpM.inputs.length > 1 && !gpM.inputs.every(inp => inp === 'gem' || inp.split('|').includes('gem'))) continue;
403-
gemProcessors.push({ id: gpId, name: gpM.name || gpId, effect: gpM.effect });
404-
}
405-
// Sort: single-input first (gem_cutter), then combine (prismatic)
406-
gemProcessors.sort((a, b) => {
407-
const am = registry.get(a.id);
408-
const bm = registry.get(b.id);
409-
return (am?.inputs?.length || 1) - (bm?.inputs?.length || 1);
410-
});
411-
for (const gp of gemProcessors) path.push(gp.name);
412-
// Polisher and QA are applied by solver if available
413-
const oreChainTags = [];
414-
// Check what tags the ore chain has by looking at ore processing machines
415-
for (const [mid, mm] of registry.machines) {
416-
if (mm.tag && registry.isAvailable(mid, config)) oreChainTags.push(mm.tag);
408+
path.push(gpM.name || gpId);
417409
}
418-
if (oreChainTags.includes("Polished") && registry.isAvailable("polisher", config)) {
410+
411+
// Step 2: Polisher (flat, before combines/QA)
412+
const polisher = registry.get("polisher");
413+
if (polisher?.tag && registry.isAvailable("polisher", config)) {
419414
path.push('Polisher');
420415
}
416+
417+
// Step 3: same-type gem combines (Prismatic)
418+
for (const [gpId, gpM] of registry.machines) {
419+
if (!registry.isAvailable(gpId, config)) continue;
420+
if (gpM.effect !== 'combine') continue;
421+
const allGem = (gpM.inputs || []).every(inp => inp === 'gem' || inp.split('|').includes('gem'));
422+
if (!allGem) continue;
423+
path.push(gpM.name || gpId);
424+
}
425+
426+
// Step 4: QA (percent, last)
421427
if (registry.isAvailable("quality_assurance", config)) {
422428
path.push('QA');
423429
}

0 commit comments

Comments
 (0)