Weapon fire rate is quantised to the 30 Hz frame and the remainder is discarded,
so the engine delivers only 1800/n rates. Between 450 and 1800 rpm there are
exactly four: 1800, 900, 600, 450. A rifle configured at 800 rpm fires at 600.
Between 900 and 1800 rpm there is no rung at all, so every weapon configured
from 901 to 1799 delivers 900. The MP7's real 950 delivers 900. A 1200 rpm
submachine gun delivers 900.
The behaviour matches nextShot = now + interval snapped to the next frame, where
a correct implementation accumulates nextShot += interval.
Two further consequences:
1. Weapons configured exactly on a frame boundary have a framerate-dependent
rate. One configured at 928 rpm delivered 764 rpm with frames uncapped and
895 rpm with them capped at 120, nothing else changed. This half reproduces on a
clean vanilla install with any 900 or 600 rpm weapon.
2. The muzzle is capped at one round per frame, which is 1800 rpm. A 4000 rpm
minigun delivered 1796.
Measured over 1,290 shot events. 84 weapons audited across vanilla, RHS and a
large weapons mod.
Full write-up is in the attached PDF, BA-reforger-fire-rate-report.pdf.
It covers the mechanism, the distribution proof, scope, the four changes with
implementation code and the failure modes to avoid, why SalvoSize is not a
substitute, and the script API that would let mods work around this if none of
it is planned.
The fix, as four independent changes. The smallest useful one is a single
character.
A. nextShot += interval instead of =. Every rate below 1800 becomes exactly
deliverable and the framerate dependence disappears. This alone resolves the
defect for all 84 weapons that are not miniguns.
B. while instead of if. Adds the five vehicle weapons above 1800 rpm.
C. Evaluate the shot schedule faster than 30 Hz. This is about first-shot
latency, currently 16.7 ms average and 33 ms worst case.
D. Let the shot sound, the projectile spawn, the recoil curve and the muzzle
flash use the shot's intended time rather than the frame's. Under sustained
fire this is indistinguishable from full sub-frame scheduling, and nothing
has to run off-tick.
Recommended: A, B and D.
None of this is novel. Valve's weapon base class already does all three.
CBaseCombatWeapon::PrimaryAttack accumulates with +=, loops with while, and
passes the intended time into WeaponSound. Its comment gives the same reason
this report does: "To make the firing framerate independent, we may have to fire
more than one bullet here on low-framerate systems, especially if the weapon
we're firing has a really fast rate of fire."
Related: DayZ T161727 (November 2021), where the 30 Hz constraint was confirmed
on the record and closed with "we currently do not plan to change this
behaviour". This is not a re-filing. It adds the framerate dependence, the empty
900 to 1800 band, the turret cap, the measurements, and a specific set of asks.