Skip to content

Timeline Service English

LA edited this page Jul 3, 2026 · 1 revision

Timeline Service (English)

中文 | English | Architecture Overview

What It Is

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.

Differences from Official Lazer

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

Data Flow

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

Sub-frame Correction Explained

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
  • Enabled toggle: 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.

What This Means for Players

  • 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

Key Concepts for Developers

  • EzScoreTimeline: Immutable object with a sorted EzScoreTimelineSnapshot[], providing QueryAtTime() 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.RunTimeline event-by-event
  • EzScoreTimelineBridge: Ruleset bridge point — currently only Mania is registered; other rulesets fall back to HitEvents path (to be deprecated)
  • Sub-frame correction: EzSubFrameCorrection is a static class with no service container dependency; Enabled toggle and thresholds are configurable

Related Links

Clone this wiki locally