fix(engine): implement input batch reservation to prevent finished goods disappearance on input shortage

This commit is contained in:
Christian 2026-10-16 00:01:18 +02:00
parent da5435afbc
commit 007fee47d4
2 changed files with 60 additions and 48 deletions

View file

@ -269,31 +269,34 @@ 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:
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['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)
if logistics > 0 and p_state.get('stock', 0.0) > 0:

View file

@ -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) {
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;
// Retain existing cycle progress until inputs arrive
} 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;
// Deduct inputs
for (const inp of p.inputs) {
if (state.products[inp.id]) {
state.products[inp.id].stock = Math.max(
0,
state.products[inp.id].stock - (inp.amount * lines * cyclesToRun)
);
}
}
// Credit finished goods
const produced = cyclesToRun * p.outputAmount * lines;
// 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;
}
// 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);
}
}
} else {
pState.isBottlenecked = true;
}
}
}