openfrontio / openfrontio/OpenFrontIO

Same-tick nuke departures after Missile Silo reload (follow-up to #4937)

Open
#5,391 1 comment 0 reactions 0 assignees View on GitHub
not-approved
Dominant language
TypeScript
Stars
2.7k
Forks
1.4k
Avg merge
17h 43m
Merged PRs (30d)
310

Description

Title

Same-tick nuke departures after Missile Silo reload (follow-up to #4937)

Describe the bug

Atom Bombs reserved on different ticks can begin moving on the same simulation tick from a single Missile Silo after older cooldown entries begin reloading.

This reproduces on OpenFrontIO v0.34.0-beta2, which already includes #4937 ("Small fix: prevent nukes from firing in same tick").

This appears to be a remaining edge case across the 90-tick Missile Silo reload boundary.

To Reproduce

The smallest reproduction uses:

- one Missile Silo
- no SAMs
- two Atom Bombs
- normal core build/upgrade execution
- no manually injected cooldown entries
- no modification of "waitTicks"

Ticks below are relative to the initialized test fixture.

1. Start with a level-1 Missile Silo.
2. Apply 50 upgrades at tick 0.
3. Apply 49 additional upgrades at tick 8, bringing the silo to level 100.
4. Submit the first Atom Bomb so that its "NukeExecution" reserves the silo at tick 18.
5. Submit the second Atom Bomb so that it reserves the silo at tick 99, after older cooldown entries have begun reloading.
6. Observe that both bombs begin moving on tick 100.

Observed timing:

- Bomb A: reserved at tick 18, receives "waitTicks = 81", first moves at tick 100
- Bomb B: reserved at tick 99, receives "waitTicks = 0", first moves at tick 100

Despite being reserved 81 ticks apart, both bombs leave the same silo on the same simulation tick.

Expected behavior

Nukes reserved from the same Missile Silo should remain staggered and should not begin moving on the same tick.

A later reservation should not receive a departure tick already assigned to an earlier, still-waiting nuke from the same silo.

Screenshots

Not applicable. This reproduces directly in the core simulation and does not depend on rendering or browser UI.

Desktop (please complete the following information):

- OS: Linux
- Browser: N/A (headless core simulation)
- Version: OpenFrontIO v0.34.0-beta2
- Commit: "1b0c0bc9288e5ad5c53331a971dbde36dd332a5c"
- Node.js: 24.19.0
- tsx: 4.22.4

Smartphone (please complete the following information):

Not applicable. This is a core simulation scheduling issue and does not depend on a smartphone or browser.

Additional context

"UnitImpl.launch()" stores reservation ticks in "missileTimerQueue". Missile Silo upgrades also add timestamps to this queue.

"MissileSiloExecution" removes at most one matured entry per tick once its age reaches 90 ticks.

When a new "NukeExecution" is created, its delay is reconstructed from the entries currently remaining in "missileTimerQueue":

let lastDep = 0;
for (const launchTick of silo.missileTimerQueue()) {
lastDep = Math.max(launchTick + 1, lastDep + 1);
}

if (lastDep > this.mg.ticks()) {
this.waitTicks += lastDep - this.mg.ticks();
}

Previously created nukes keep their already assigned "waitTicks".

In the reproduction above:

- At tick 18, the queue produces "lastDep = 99", so Bomb A receives 81 wait ticks and will begin moving on tick 100.
- Between ticks 18 and 99, older entries reload and are removed from the queue.
- At tick 99, the shortened queue produces "lastDep = 90".
- Since "lastDep <= currentTick", Bomb B receives no additional wait and also begins moving on tick 100.

Importantly, Bomb A's own reservation entry is still present at tick 99. Removing older entries alone is enough for the reconstructed "lastDep" to fall behind the departure schedule already assigned to Bomb A.

I also reproduced the same behavior without upgrades during the launch sequence: from a fully reloaded level-91 silo with an initially empty cooldown queue, 91 Atom Bombs reserved at tick 2 followed by one additional bomb at tick 92 result in the last original bomb and the new bomb both beginning movement on tick 93. A level-90 control case does not overlap.

This appears to be a follow-up to #4937 rather than the original fix being absent. The regression test added by #4937 completes before any 90-tick Missile Silo reload boundary is reached.

Contributor guide

Open the contributing guide

Research direction

Start by tracing UnitImpl.launch(), missileTimerQueue, MissileSiloExecution, and NukeExecution, then inspect the regression test added for #4937. Reproduce the described level-91 and reload-boundary cases in the headless core simulation. Done means nukes reserved from one silo never begin moving on the same tick, including after older queue entries reload.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
game-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.