The problem I needed to solve
A match does not gain five minutes of play when a player spends five minutes outside the app. I separated active duration from wall-clock duration in match reporting.
Implementation in this snapshot
ForegroundMatchClock receives a now function. Seconds combines accumulated time with the active interval; Pause freezes it and Resume establishes a new interval origin.
The design decision and its cost
An injected clock makes the logic explainable without a direct MonoBehaviour dependency. Repeated Pause or Resume calls do not add effects, and negative values are clamped.
Current boundaries
Correctness depends on a monotonic clock and properly wired pause lifecycle. The class cannot detect backgrounding by itself and does not persist across process restarts.
Review scenarios and contract meaning
A match with several minutes of play and several minutes outside the application has two different durations. The foreground clock sums active intervals, which need not equal the wall-clock difference between start and end. This is an explanatory example rather than real report data. Idempotent Pause and Resume calls help prevent similar callbacks from accumulating time twice. Correct application lifecycle wiring remains necessary: the class cannot know on its own whether the window was actually foregrounded. Its injected time function must also match the expectation of monotonic time.
PHP reporting can read game_progress checkpoints and game_end through Latest record per entity for a match duration. The selection key combines player_id with the match identifier so matching identifiers across players do not merge. Event selection and filters run before choosing the newest timestamp. Date ranges use the event’s UTC time rather than the match’s starting day. A match ending the next day is consequently not automatically joined to all starts from the preceding day. A checkpoint without an ending shows only reported time; the remaining duration is not guessed. That boundary matters when interpreting the metric honestly.
A final checkpoint and an actual ending are different observations. An interrupted game may leave only partial evidence of active time. I read that as reported time without guessing completion or a win from missing events. Keeping ending reason, active duration and final-event presence separate avoids confusing incomplete data with a short successful experience.
What this stage taught me
For me, time definition became part of metric definition. Foreground duration can explain play better than two timestamps’ difference, but should be interpreted alongside ending reasons and session state.




