diff --git a/backend/engine.py b/backend/engine.py index 6cf4c13..13a90b6 100644 --- a/backend/engine.py +++ b/backend/engine.py @@ -269,30 +269,33 @@ def process_offline_and_ticks(state: dict) -> dict: p_state['stock'] = p_state.get('stock', 0.0) + produced p_state['totalProduced'] = p_state.get('totalProduced', 0.0) + produced else: - # Manufacturing requiring inputs - max_cycles = int(min( - state['products'].get(inp['id'], {}).get('stock', 0.0) // (inp['amount'] * lines) - for inp in inputs - )) if inputs else 0 + # Manufacturing logic with input batch reservation + needed = [(inp['id'], inp['amount'] * lines) for inp in inputs] + can_start = all(state['products'].get(item_id, {}).get('stock', 0.0) >= req for item_id, req in needed) - if max_cycles <= 0: - p_state['isBottlenecked'] = True + if p_state.get('cycleProgress', 0.0) <= 0.0: + if not can_start: + p_state['isBottlenecked'] = True + else: + p_state['isBottlenecked'] = False + for item_id, req in needed: + state['products'][item_id]['stock'] = max(0.0, state['products'][item_id].get('stock', 0.0) - req) + p_state['cycleProgress'] = p_state.get('cycleProgress', 0.0) + (cycle_rate * dt) else: p_state['isBottlenecked'] = False p_state['cycleProgress'] = p_state.get('cycleProgress', 0.0) + (cycle_rate * dt) if p_state['cycleProgress'] >= 1.0: - cycles_to_run = min(int(p_state['cycleProgress']), max_cycles) - if cycles_to_run > 0: - p_state['cycleProgress'] -= cycles_to_run - for inp in inputs: - if inp['id'] in state['products']: - state['products'][inp['id']]['stock'] = max( - 0.0, - state['products'][inp['id']]['stock'] - (inp['amount'] * lines * cycles_to_run) - ) - produced = cycles_to_run * p_cfg['outputAmount'] * lines - p_state['stock'] = p_state.get('stock', 0.0) + produced - p_state['totalProduced'] = p_state.get('totalProduced', 0.0) + produced + p_state['cycleProgress'] = 0.0 + produced = p_cfg['outputAmount'] * lines + p_state['stock'] = p_state.get('stock', 0.0) + produced + p_state['totalProduced'] = p_state.get('totalProduced', 0.0) + produced + + # Check next batch + if can_start: + for item_id, req in needed: + state['products'][item_id]['stock'] = max(0.0, state['products'][item_id].get('stock', 0.0) - req) + else: + p_state['isBottlenecked'] = True # Passive Logistics Sales logistics = p_state.get('logistics', 0) diff --git a/frontend/js/core/engine.js b/frontend/js/core/engine.js index 6c97f1b..faa061e 100644 --- a/frontend/js/core/engine.js +++ b/frontend/js/core/engine.js @@ -67,45 +67,54 @@ export class SimulationEngine { } } } else { - // Check if we have required input stock - const maxCyclesPossible = Math.min( - ...p.inputs.map(inp => { - const inputStock = state.products[inp.id]?.stock || 0; - const reqPerCycle = inp.amount * lines; - return reqPerCycle > 0 ? Math.floor(inputStock / reqPerCycle) : 0; - }) - ); + // Manufacturing logic with input batch reservation: + // Check if factory is currently executing an in-progress cycle or needs to start one + const neededInputs = p.inputs.map(inp => ({ + id: inp.id, + amount: inp.amount * lines + })); - if (maxCyclesPossible <= 0) { - pState.isBottlenecked = true; - // Retain existing cycle progress until inputs arrive + const canStartBatch = neededInputs.every(inp => (state.products[inp.id]?.stock || 0) >= inp.amount); + + if (pState.cycleProgress <= 0.0) { + // Not currently running a cycle + if (!canStartBatch) { + pState.isBottlenecked = true; + } else { + // Start batch by reserving required inputs + pState.isBottlenecked = false; + for (const inp of neededInputs) { + if (state.products[inp.id]) { + state.products[inp.id].stock = Math.max(0, state.products[inp.id].stock - inp.amount); + } + } + pState.cycleProgress += cycleRate * dt; + } } else { + // Cycle already in progress (inputs already secured) pState.isBottlenecked = false; pState.cycleProgress += cycleRate * dt; if (pState.cycleProgress >= 1.0) { - const cyclesToRun = Math.min(Math.floor(pState.cycleProgress), maxCyclesPossible); - if (cyclesToRun > 0) { - pState.cycleProgress -= cyclesToRun; + // Finished cycle, deliver produced goods + pState.cycleProgress = 0.0; + const produced = p.outputAmount * lines; + pState.stock += produced; + pState.totalProduced = (pState.totalProduced || 0) + produced; + state.stats.totalProduced = (state.stats.totalProduced || 0) + produced; + if (this._sectorAccumulators[p.sector]) { + this._sectorAccumulators[p.sector].units += produced; + } - // Deduct inputs - for (const inp of p.inputs) { + // Immediately check if next batch can start + if (canStartBatch) { + for (const inp of neededInputs) { if (state.products[inp.id]) { - state.products[inp.id].stock = Math.max( - 0, - state.products[inp.id].stock - (inp.amount * lines * cyclesToRun) - ); + state.products[inp.id].stock = Math.max(0, state.products[inp.id].stock - inp.amount); } } - - // Credit finished goods - const produced = cyclesToRun * p.outputAmount * lines; - pState.stock += produced; - pState.totalProduced = (pState.totalProduced || 0) + produced; - state.stats.totalProduced = (state.stats.totalProduced || 0) + produced; - if (this._sectorAccumulators[p.sector]) { - this._sectorAccumulators[p.sector].units += produced; - } + } else { + pState.isBottlenecked = true; } } }