RootMC SMP

Home · Time

Clocks · crons · harvest

How RootMC keeps time

RootMC does not use vanilla “daylight cycle = 20 minutes” as the source of truth for economy days, Towny upkeep, bond coupons, or playtime. Several clocks run in parallel, then get reconciled into MySQL, D1, Discord, and the live boards. This page is the technical map.

Live peaks, online charts, and playtime tables still live at /time/old/ (the previous /time/ dashboard).

1. Clock layers

LayerAuthorityWhat it drives
Wall clock Host OS → JVM → IANA zone (Pacific/Honolulu on live) McDay alignment, midnight rollover, cron gates
McDay McDayClock in Root-Times / common World fullTime, phase, Towny tax day, bonds settle, upkeep
Vanilla ticks Paper world time, driven from McDay Lighting, mobs, crops (growth scaled to day length)
HST calendar API math (UTC−10 fixed offset) Treasury months, weekly awards week keys, daily report “day”
Player TZ band IP → offset band + Discord TZ roles Activity pies, “peak in your clock”, welcome line
Playtime counters MySQL harvest tables /playtime, leaderboards, Official Towny+Claims totals

2. Minecraft day (McDayClock)

Implemented in com.rootrecord.minecraft.common.McDayClock, configured from plugins/RootMC/root-times.ymlminecraft-day.

Live production defaults (both hosts):

minecraft-day:
  enabled: true
  timezone: "Pacific/Honolulu"   # HST / UTC−10, no DST
  length-minutes: 20             # one MC day = 20 real minutes
  midday-minute: 0               # xx:00 → in-game noon (tick 6000)
  midnight-minute: 15            # xx:15 → in-game midnight (tick 18000)

Day ID formula

Absolute day id from wall time in the configured zone:

epochMinutes = localDate.toEpochDay × 1440 + hour × 60 + minute
absoluteDayId = floor( (epochMinutes − midnightMinute) / lengthMinutes )

Host-facing day id = max(0, absoluteDayId − dayIdBase). Towny uses day-id-base: 0 (absolute network days). Claims uses day-id-base: auto (local day 0 persisted so the Claims world can reset without sharing Towny’s huge day number).

Tick mapping

With length-minutes: 20 and midnight at :15, there are 3 Minecraft days per real hour, and 72 per real day. Vanilla would be the same length, but RootMC’s midnight is locked to wall-clock :15, not “whenever the server started.”

3. Towny vs Claims day IDs

TownyClaims
Bucket playtime.server-bucket: towny playtime.server-bucket: claims
Day id base 0 (absolute) auto (local epoch file)
UI label “Towny” day card on live metrics “Claims” day card
Shared zone Pacific/Honolulu, 20‑minute days, midnight at :15

Official playtime totals sum both buckets (*). Per-host /playtime breakdowns stay scoped so a Claims session does not inflate Towny-only boards.

4. Towny new-day bridge

Stock Towny schedules its own daily timer. RootMC disables that timer and fires Towny’s new-day task from the McDay midnight edge instead (TownyMcDayBridge in Root-Economy):

Same McDay tick also kicks bond coupon settlement catch-up, town inactivity tax, and economy heartbeat hooks (processMcDayRollover / processMcDayTownTax / …).

5. HST calendars (API / treasury / awards)

The Cloudflare Worker treats HST as a fixed UTC−10 offset (no DST), matching Honolulu for all practical purposes. This is not the same object as McDayClock, but it uses the same zone intent.

# Worker crons (api.rootmc.net) — UTC expressions
*/10 * * * *     # every 10 minutes (watchdogs + report retries)
0 * * * *        # hourly at :00
30 10 * * *      # daily 10:30 UTC = 00:30 HST
30 10 1 * *      # 1st of month 10:30 UTC = 00:30 HST
0 18 * * 1       # Monday 18:00 UTC = Monday 08:00 HST

6. How playtime & activity are collected

Root-Times harvest loop (harvest.* in root-times.yml):

SignalSourceSink
Playtime seconds Online non-AFK ticks, flushed every flush-interval-seconds (60s) MySQL {prefix}playtime + monthly rollup
Activity by TZ Same harvest, attributed to player’s timezone band {prefix}activity_timezone, hourly buckets
AFK No movement / interaction for afk.after-seconds (300s) Excluded from playtime & activity when configured; sessions table
IP → TZ ip-api.com offset (cached 24h) Default band if player has not set /timezone

Sync path: Paper → Shockbyte MySQL → (hourly Worker pull / plugin cloud sync) → D1 → /api/rootmc/time/* for the web. Official totals merge Towny + Claims buckets without double-counting wall time across hosts incorrectly — each host writes its own bucket.

7. Cloud presence & online samples

8. Cloudflare Worker crons (detail)

Every scheduled invocation also runs lightweight reconcile work (Towny Discord channels, proposal expiry, shop alerts, Discord activity sync, legislature). Then, by expression:

Cron (UTC)Jobs
*/10 * * * * Connection preference watchdog; dev workstation timeout; host presence maintenance; automated-reports board refresh near :00; daily/weekly report retry gates
0 * * * * MySQL → D1 economy pull; webstat pull; combined Gen1+Gen2 hourly economy Discord snapshot
30 10 * * * Aligned with 00:30 HST — daily report suite window
30 10 1 * * Monthly treasury dividend slot (1st, 00:30 HST)
0 18 * * 1 Monday 08:00 HST slot (scheduled Worker beat; weekly awards themselves gate on Sunday 08:00 HST via the 10‑min retry)

Public /reserve pages intentionally stay on D1-only reads so Hyperdrive stalls cannot abort the browser; MySQL enrichment is cron/sync territory.

9. Public time APIs

EndpointPurpose
GET /api/rootmc/time/local?tz= Viewer-local peak / quiet window, McDay cards for Towny + Claims
GET /api/rootmc/time/charts?tz= Timezone activity share + supporting series
Server online series Presence samples for the players-online chart ranges
Playtime / weekly boards Leaderboard data shared with /leaderboard/

Pass tz as an IANA id (from the browser) so peaks remapped into “your clock.” Server-side bands still use the fixed RootMC TZ key list (UTC−12 … UTC+14 style roles).

10. In-game commands

Want charts? Open the live time metrics dashboard · Host health · Leaderboards · Constitution (governance voting also weights playtime).