Repository navigation
Timeline Service English
中文 | English | Architecture Overview
The Timeline Service converts "a score replay" into a binary-searchable array of snapshots. When a historical score is loaded in song select, the service immediately builds its timeline and caches it. During gameplay, Tournament HUD components query the timeline at the current game clock time to retrieve the score, ACC, combo, and miss count at that moment.
It is the data foundation for the Tournament Service — without timelines, ghost comparison cannot function.
| Aspect | Official Lazer | Ez2Lazer Timeline Service |
|---|---|---|
| Score Timeline | Aggregated at results screen only | O(log n) binary query: snapshot at any moment |
| Cache Strategy | None | In-process memory cache, auto-isolated per session scope |
| Cache Isolation | No judgement mode distinction | Key includes hitMode:healthMode:judgePrecedence — different environments don't pollute each other |
| Frame Rate Compensation | None | Sub-frame correction: key press timestamp minus FSC frame clock update wall clock delta |
| Build Method | No independent build path | Two paths: ManiaSession (primary) / HitEvents (legacy, to be deprecated) |
| Cache Limit | None | LRU max 3 maps, 300ms debounce |
ScoreInfo
│
▼
EzScoreTimelineBuilder
│ Parse score → Build snapshot array
│ Cache key = beatmapId:mods:dificulty:hitMode:healthMode:judgePrecedence
▼
EzScoreTimeline (immutable)
│ EzScoreTimelineSnapshot[] + QueryAtTime(clockTime) → O(log n)
│
├── Tournament Service → Three-Pillar HUD / Leaderboard queries per frame
└── Sub-frame correction → EzSubFrameCorrection corrects key press offset
Problem: FSC (FrameStabilityContainer) only updates its ManualClock once per frame. If a key press occurs between frames, the clock still contains the previous frame's value, introducing ±(frame_time/2) error (~±1.5ms at 300fps, up to ±8ms at 60fps).
Solution: Record the wall clock timestamp each time FSC updates its clock. When a key is pressed, also record the wall clock timestamp — the difference is the sub-frame correction value.
-
RecordFscUpdate(): Called by FSC.updateClock -
GetCorrectionMs(keyWallTimestamp, rate): Returns correction value, range 0~20ms -
Enabledtoggle: On by default, can be disabled in settings
Player impact: Minimal difference at high frame rates; at low frame rates (~60fps), judgement feels more accurate with fewer "I pressed it but got a Bad" disconnects.
- Zero-delay comparison loading: Timelines are cached on second play of the same map — opening Three-Pillar HUD or leaderboard won't lag
- Lower frame rate = bigger sub-frame correction benefit: Laptops or low-end PCs feel more responsive for Mania gameplay
- Different HitMode timelines don't interfere: Switching between O2Jam and BMS modes won't confuse the cache
-
EzScoreTimeline: Immutable object with a sorted
EzScoreTimelineSnapshot[], providingQueryAtTime()binary search - EzScoreTimelineBuilder: Static entry point that reads scores from ScoreManager + BeatmapManager, selecting build path (ManiaSession / HitEvents)
-
IEzScoreTimelineCache: In-process memory cache;
CreateSessionCache()obtains an instance released at session end -
ManiaTimelineBuilder: Mania-specific timeline implementation using
ManiaReplaySession.RunTimelineevent-by-event - EzScoreTimelineBridge: Ruleset bridge point — currently only Mania is registered; other rulesets fall back to HitEvents path (to be deprecated)
-
Sub-frame correction:
EzSubFrameCorrectionis a static class with no service container dependency;Enabledtoggle and thresholds are configurable
- Tournament Service — The consumer of timelines
- HUD Widgets - Three-Pillar HUD — Visual representation of timelines