Rendering a responsive fight and calculating a consistent match result are separate responsibilities. OfficeFighters assigns them to Angular application services, a Phaser scene, a pure .NET simulation and its hosting layer. The interesting engineering problem is how those parts share one match while operating on different clocks.
Give the game a narrow application boundary
Angular handles navigation, authentication, management screens and transport lifecycle. Phaser handles the visual scene. NetBridge defines the game's network boundary, while HubNetBridge adapts SignalR on the application side. The scene can consume game data without knowing Angular injection or connection construction.
That boundary also gives cleanup an owner. When a scene is destroyed, its listeners and resources need to stop. Otherwise a later scene can receive the same state alongside the abandoned listener, producing duplicate processing or effects.
State the simulation units explicitly
SimMath uses one thousand mpx per pixel and tick-based time. Percentage arithmetic truncates toward zero and uses a long intermediate before clamping into the int range.
// SimMath: source arithmetic, with a 64-bit intermediate.
public const int MpxPerPx = 1000;
public static int Pct(int value, int pct) =>
Saturate((long)value * pct / 100);
// C# integer division truncates toward zero:
// Pct(-7, 50) == -3Negative inputs matter when testing this arithmetic: fifty percent of -7 becomes -3 under truncation, while floor gives -4. Positive-only test cases would miss the difference. Determinism also depends on operation ordering and random inputs, so integer coordinates alone are not a complete proof.
One scheduling owner per room
GameLoopShard updates its rooms sequentially on a dedicated thread. Automatic shard selection clamps half the processor count between one and four, and ShardPool assigns a new room to the least-loaded shard. Incoming work enters queues; the room loop owns simulation mutation.
Keeping a room on one shard avoids concurrent callers independently advancing its state. Capacity still depends on the cost and distribution of rooms. Iteration duration, lateness and dropped-tick counters expose that cost rather than assuming that another thread automatically fixes it.
Bound catch-up work
GameRoom schedules ticks against a monotonic clock. If it falls behind, it performs consecutive steps up to MaxCatchUpTicks. Remaining overdue work is counted as dropped ticks and the schedule moves ahead. This bounds the recovery work performed in one update. Wall-clock changes do not define elapsed gameplay time.
Render between snapshots
The room derives a snapshot interval from the configured tick and snapshot rates. The browser stores received states and brackets renderTick with an earlier and a later snapshot. Linear interpolation supplies intermediate positions.
// Interpolation math used by the renderer.
const t = Math.min(1, Math.max(0,
(renderTick - a.tick) / (b.tick - a.tick)));
const x = a.x + (b.x - a.x) * t;
// a: tick 100, x 1000; b: tick 102, x 1400
// renderTick 101 => t 0.5 => x 1200The implementation interpolates x, y, vx and vy. Discrete fields such as health, facing, move index and fighter state come from the earlier snapshot. Averaging a movement identifier would have no useful gameplay meaning.
Respect transitions and missing data
A round change disables position interpolation between those states so the reset does not look like a character gliding across the arena. stateTick can advance visually while the same move continues, but hitstop holds it steady. When the buffer runs out, the renderer holds the last confirmed snapshot and reports starvation.
Focused cases include equal ticks, negative coordinates, round changes, hitstop and a starved buffer. The repository's netcode tests exercise these calculations separately from Phaser rendering. This source review explains the design; it does not report a device frame-rate measurement or a measured player-capacity result.
Kaynaklar / References
More in the OfficeFighters series
- OfficeFighters — managing game assets with Supabase Storage and S3
- OfficeFighters — real-time game communication with SignalR and WebSocket
- OfficeFighters — resilient email delivery with MailKit and an outbox
- OfficeFighters — JWT, refresh-token rotation and multi-tab session management
- OfficeFighters — PostgreSQL, EF Core and a DB-first data model