Add Net Supply Flow balance indicators to facility cards and inspector
This commit is contained in:
parent
f94d7df8f8
commit
638947f04c
3 changed files with 62 additions and 17 deletions
|
|
@ -1,8 +1,6 @@
|
||||||
// Production - The Idle Game: Desktop Inspector & Supply Chain Analytics Panel
|
|
||||||
// Fine-grained DOM updates without innerHTML teardowns
|
|
||||||
|
|
||||||
import { PRODUCTS, SECTORS, getProduct } from '../data/products.js';
|
import { PRODUCTS, SECTORS, getProduct } from '../data/products.js';
|
||||||
import { fmtCurrency, fmtNum } from '../core/formatters.js';
|
import { fmtCurrency, fmtNum } from '../core/formatters.js';
|
||||||
|
import { computeProductNetFlow } from './production/ratesCalculator.js';
|
||||||
|
|
||||||
let currentMountedProductId = null;
|
let currentMountedProductId = null;
|
||||||
|
|
||||||
|
|
@ -18,6 +16,7 @@ export function mountInspector(container, state, onAction) {
|
||||||
|
|
||||||
const totalLiquidationValue = (pState.stock || 0) * product.basePrice;
|
const totalLiquidationValue = (pState.stock || 0) * product.basePrice;
|
||||||
const halfLiquidationValue = totalLiquidationValue * 0.5;
|
const halfLiquidationValue = totalLiquidationValue * 0.5;
|
||||||
|
const netFlow = computeProductNetFlow(product, state);
|
||||||
|
|
||||||
let html = `
|
let html = `
|
||||||
<div class="inspector-card">
|
<div class="inspector-card">
|
||||||
|
|
@ -43,12 +42,14 @@ export function mountInspector(container, state, onAction) {
|
||||||
<strong class="metric-val font-mono">${fmtCurrency(product.basePrice)}</strong>
|
<strong class="metric-val font-mono">${fmtCurrency(product.basePrice)}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric-box">
|
<div class="metric-box">
|
||||||
<span class="metric-label">Production Lines:</span>
|
<span class="metric-label">Net Supply Flow:</span>
|
||||||
<strong class="metric-val font-mono" id="insp-lines">${pState.lines} active</strong>
|
<strong class="metric-val font-mono ${netFlow.netRate < -0.05 ? 'text-warning' : 'text-profit'}" id="insp-net-rate">
|
||||||
|
${netFlow.netRate > 0.01 ? '+' : ''}${fmtNum(netFlow.netRate, 1)}/s
|
||||||
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric-box">
|
<div class="metric-box">
|
||||||
<span class="metric-label">Logistics Fleets:</span>
|
<span class="metric-label">Logistics Fleets:</span>
|
||||||
<strong class="metric-val font-mono" id="insp-logistics">${pState.logistics || 0} active</strong>
|
<strong class="metric-val font-mono" id="insp-logistics">${pState.logistics || 0} active (${fmtNum((pState.logistics || 0) * 0.75, 2)}/s)</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -181,6 +182,20 @@ export function updateInspector(container, state) {
|
||||||
const btnAll = document.getElementById('insp-btn-all');
|
const btnAll = document.getElementById('insp-btn-all');
|
||||||
if (btnAll) btnAll.disabled = pState.stock < 1;
|
if (btnAll) btnAll.disabled = pState.stock < 1;
|
||||||
|
|
||||||
|
// Update Net Flow rate
|
||||||
|
const netFlow = computeProductNetFlow(product, state);
|
||||||
|
const elNetRate = document.getElementById('insp-net-rate');
|
||||||
|
if (elNetRate) {
|
||||||
|
const sign = netFlow.netRate > 0.01 ? '+' : '';
|
||||||
|
const netText = `${sign}${fmtNum(netFlow.netRate, 1)}/s`;
|
||||||
|
if (elNetRate.textContent !== netText) {
|
||||||
|
elNetRate.textContent = netText;
|
||||||
|
}
|
||||||
|
const isDeficit = netFlow.netRate < -0.05;
|
||||||
|
const isSurplus = netFlow.netRate > 0.05;
|
||||||
|
elNetRate.className = `metric-val font-mono ${isDeficit ? 'text-warning' : (isSurplus ? 'text-profit' : '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Update input values
|
// Update input values
|
||||||
if (product.inputs) {
|
if (product.inputs) {
|
||||||
for (const inp of product.inputs) {
|
for (const inp of product.inputs) {
|
||||||
|
|
@ -191,3 +206,4 @@ export function updateInspector(container, state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Production UI: Individual Facility Card (HTML Mount and Fine-grained DOM update)
|
// Production UI: Individual Facility Card (HTML Mount and Fine-grained DOM update)
|
||||||
import { getProduct, getNextMilestone, getUnitMilestoneMultiplier, SECTORS } from '../../data/products.js';
|
import { getProduct, getNextMilestone, getUnitMilestoneMultiplier, SECTORS } from '../../data/products.js';
|
||||||
import { fmtCurrency, fmtNum } from '../../core/formatters.js';
|
import { fmtCurrency, fmtNum } from '../../core/formatters.js';
|
||||||
import { calculateMultiCost, getProductOutputRates } from './ratesCalculator.js';
|
import { calculateMultiCost, getProductOutputRates, computeProductNetFlow } from './ratesCalculator.js';
|
||||||
|
|
||||||
export function renderFacilityCardHtml(p, pState, state, buyMode) {
|
export function renderFacilityCardHtml(p, pState, state, buyMode) {
|
||||||
const sector = SECTORS.find(s => s.id === p.sector);
|
const sector = SECTORS.find(s => s.id === p.sector);
|
||||||
|
|
@ -113,6 +113,10 @@ export function renderFacilityCardHtml(p, pState, state, buyMode) {
|
||||||
${rates.isBottlenecked ? '0.0 (Paused)' : `${fmtNum(rates.prodRate, 1)}/s`}
|
${rates.isBottlenecked ? '0.0 (Paused)' : `${fmtNum(rates.prodRate, 1)}/s`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-cell" title="Net Supply Flow = Factory Production minus Downstream Input Consumption minus Logistics Wholesale Sales">
|
||||||
|
<span class="stat-label">Net Balance:</span>
|
||||||
|
<span class="stat-value font-mono text-profit" id="net-flow-${p.id}">+0.0/s</span>
|
||||||
|
</div>
|
||||||
<div class="stat-cell">
|
<div class="stat-cell">
|
||||||
<span class="stat-label">Logistics Rev:</span>
|
<span class="stat-label">Logistics Rev:</span>
|
||||||
<span class="stat-value font-mono text-profit" id="rate-rev-${p.id}">+$${fmtNum(rates.revRate, 2)}/s</span>
|
<span class="stat-value font-mono text-profit" id="rate-rev-${p.id}">+$${fmtNum(rates.revRate, 2)}/s</span>
|
||||||
|
|
@ -125,10 +129,6 @@ export function renderFacilityCardHtml(p, pState, state, buyMode) {
|
||||||
<span class="stat-label">Logistics Auto-Sell:</span>
|
<span class="stat-label">Logistics Auto-Sell:</span>
|
||||||
<span class="stat-value font-mono" id="logistics-${p.id}">${pState.logistics || 0} fleets (${fmtNum((pState.logistics || 0) * 0.75, 2)}/s)</span>
|
<span class="stat-value font-mono" id="logistics-${p.id}">${pState.logistics || 0} fleets (${fmtNum((pState.logistics || 0) * 0.75, 2)}/s)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-cell">
|
|
||||||
<span class="stat-label">Milestone Eff:</span>
|
|
||||||
<span class="stat-value font-mono" id="eff-${p.id}">${milestoneMult}x Speed</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${inputsHtml}
|
${inputsHtml}
|
||||||
|
|
@ -190,6 +190,20 @@ export function updateFacilityCardDom(p, pState, state, buyMode) {
|
||||||
elRateUnits.className = `stat-value font-mono ${rates.isBottlenecked ? 'text-warning' : 'text-profit'}`;
|
elRateUnits.className = `stat-value font-mono ${rates.isBottlenecked ? 'text-warning' : 'text-profit'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Live Net Flow (Production minus Downstream Consumption minus Logistics Sales)
|
||||||
|
const netFlow = computeProductNetFlow(p, state);
|
||||||
|
const elNetFlow = document.getElementById(`net-flow-${p.id}`);
|
||||||
|
if (elNetFlow) {
|
||||||
|
const sign = netFlow.netRate > 0.01 ? '+' : '';
|
||||||
|
const netText = `${sign}${fmtNum(netFlow.netRate, 1)}/s`;
|
||||||
|
if (elNetFlow.textContent !== netText) {
|
||||||
|
elNetFlow.textContent = netText;
|
||||||
|
}
|
||||||
|
const isDeficit = netFlow.netRate < -0.05;
|
||||||
|
const isSurplus = netFlow.netRate > 0.05;
|
||||||
|
elNetFlow.className = `stat-value font-mono ${isDeficit ? 'text-warning' : (isSurplus ? 'text-profit' : '')}`;
|
||||||
|
}
|
||||||
|
|
||||||
const elRateRev = document.getElementById(`rate-rev-${p.id}`);
|
const elRateRev = document.getElementById(`rate-rev-${p.id}`);
|
||||||
if (elRateRev) {
|
if (elRateRev) {
|
||||||
const revText = `+$${fmtNum(rates.revRate, 2)}/s`;
|
const revText = `+$${fmtNum(rates.revRate, 2)}/s`;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
// Production UI: Cost & Rate Calculations
|
import { PRODUCTS, getNextMilestone, getUnitMilestoneMultiplier } from '../../data/products.js';
|
||||||
import { getNextMilestone, getUnitMilestoneMultiplier } from '../../data/products.js';
|
|
||||||
|
|
||||||
export function calculateMultiCost(baseCost, currentCount, multiplier) {
|
export function calculateMultiCost(baseCost, currentCount, multiplier) {
|
||||||
const growth = 1.15;
|
const growth = 1.15;
|
||||||
|
|
@ -64,16 +63,32 @@ export function computeProductNetFlow(product, state) {
|
||||||
|
|
||||||
// Calculate how much active downstream factories consume per second
|
// Calculate how much active downstream factories consume per second
|
||||||
let consumedRate = 0;
|
let consumedRate = 0;
|
||||||
// Import PRODUCTS dynamically from caller or check through global/state if needed
|
for (const otherP of PRODUCTS) {
|
||||||
// In our architecture, caller passes or we compute from state.products and dependencies
|
if (!otherP.inputs) continue;
|
||||||
|
const inpMatch = otherP.inputs.find(inp => inp.id === product.id);
|
||||||
|
if (inpMatch) {
|
||||||
|
const otherState = state.products[otherP.id];
|
||||||
|
if (otherState && otherState.unlocked && otherState.lines > 0 && !otherState.isBottlenecked) {
|
||||||
|
const otherRates = getProductOutputRates(otherP, otherState, state);
|
||||||
|
const cyclesPerSec = otherRates.prodRate / (otherP.outputAmount * otherState.lines);
|
||||||
|
consumedRate += inpMatch.amount * otherState.lines * cyclesPerSec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const logisticsUnits = pState.logistics || 0;
|
const logisticsUnits = pState.logistics || 0;
|
||||||
const maxSellCapacity = logisticsUnits * 0.75;
|
const maxSellCapacity = logisticsUnits * 0.75;
|
||||||
const soldRate = Math.min(maxSellCapacity, pState.stock > 0 ? maxSellCapacity : effectiveProd);
|
const availableForSale = Math.max(0, effectiveProd - consumedRate);
|
||||||
|
const soldRate = Math.min(maxSellCapacity, pState.stock > 0 ? maxSellCapacity : availableForSale);
|
||||||
|
|
||||||
|
const netRate = effectiveProd - consumedRate - soldRate;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
prodRate: effectiveProd,
|
prodRate: effectiveProd,
|
||||||
|
consumedRate,
|
||||||
soldRate,
|
soldRate,
|
||||||
netRate: effectiveProd - soldRate
|
netRate
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue