Add Net Supply Flow balance indicators to facility cards and inspector

This commit is contained in:
Christian 2026-10-16 01:23:54 +02:00
parent f94d7df8f8
commit 638947f04c
3 changed files with 62 additions and 17 deletions

View file

@ -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 { fmtCurrency, fmtNum } from '../core/formatters.js';
import { computeProductNetFlow } from './production/ratesCalculator.js';
let currentMountedProductId = null;
@ -18,6 +16,7 @@ export function mountInspector(container, state, onAction) {
const totalLiquidationValue = (pState.stock || 0) * product.basePrice;
const halfLiquidationValue = totalLiquidationValue * 0.5;
const netFlow = computeProductNetFlow(product, state);
let html = `
<div class="inspector-card">
@ -43,12 +42,14 @@ export function mountInspector(container, state, onAction) {
<strong class="metric-val font-mono">${fmtCurrency(product.basePrice)}</strong>
</div>
<div class="metric-box">
<span class="metric-label">Production Lines:</span>
<strong class="metric-val font-mono" id="insp-lines">${pState.lines} active</strong>
<span class="metric-label">Net Supply Flow:</span>
<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 class="metric-box">
<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>
@ -181,6 +182,20 @@ export function updateInspector(container, state) {
const btnAll = document.getElementById('insp-btn-all');
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
if (product.inputs) {
for (const inp of product.inputs) {
@ -191,3 +206,4 @@ export function updateInspector(container, state) {
}
}
}

View file

@ -1,7 +1,7 @@
// Production UI: Individual Facility Card (HTML Mount and Fine-grained DOM update)
import { getProduct, getNextMilestone, getUnitMilestoneMultiplier, SECTORS } from '../../data/products.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) {
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`}
</span>
</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">
<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>
@ -125,10 +129,6 @@ export function renderFacilityCardHtml(p, pState, state, buyMode) {
<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>
</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>
${inputsHtml}
@ -190,6 +190,20 @@ export function updateFacilityCardDom(p, pState, state, buyMode) {
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}`);
if (elRateRev) {
const revText = `+$${fmtNum(rates.revRate, 2)}/s`;

View file

@ -1,5 +1,4 @@
// Production UI: Cost & Rate Calculations
import { getNextMilestone, getUnitMilestoneMultiplier } from '../../data/products.js';
import { PRODUCTS, getNextMilestone, getUnitMilestoneMultiplier } from '../../data/products.js';
export function calculateMultiCost(baseCost, currentCount, multiplier) {
const growth = 1.15;
@ -64,16 +63,32 @@ export function computeProductNetFlow(product, state) {
// Calculate how much active downstream factories consume per second
let consumedRate = 0;
// Import PRODUCTS dynamically from caller or check through global/state if needed
// In our architecture, caller passes or we compute from state.products and dependencies
for (const otherP of PRODUCTS) {
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 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 {
prodRate: effectiveProd,
consumedRate,
soldRate,
netRate: effectiveProd - soldRate
netRate
};
}