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
| Layer | Authority | What 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.yml → minecraft-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
- Vanilla day = 24 000 ticks. Noon = 6000, midnight = 18000.
- First half of the real-time day → midnight→noon; second half → noon→midnight.
fullTime = dayId × 24000 + timeOfDayTicks— written into the world so clients see a continuous sky.- Crop / random-tick growth is scaled so a longer wall-clock day does not under-grow plants:
RANDOM_TICK_SPEED ≈ 3 × (20 / lengthMinutes).
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
| Towny | Claims | |
|---|---|---|
| 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):
- On enable:
disableTownyDailyTimer(so Towny does not drift off McDay). - On McDay rollover:
runNewDay→ town/nation upkeep, plot taxes, etc. land on the same beat as bonds/upkeep plugins. - Towny
config.ymlday length is documented to matchminecraft-day.length-minutes(20).
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.
- Treasury months — ledger charts and
/reservemonth pickers key on HSTYYYY-MM. Post-reset metrics start2026-07. - Weekly awards — HST week = Monday 00:00 → Sunday 23:59:59. Due after Sunday 08:00 HST (18:00 UTC Sunday); retried on the 10‑minute cron until D1 has the post.
- Daily AI reports — retried after 00:30 HST until the prior day’s suite is in D1.
- Activity dividend — monthly cron slot
30 10 1 * *UTC (= 00:30 HST on the 1st).
# 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):
| Signal | Source | Sink |
|---|---|---|
| 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
cloud-status.interval-seconds: 5— Root-Times pushes online/AFK/McDay snapshot toward the API.- API presence samples are rate-limited (~1/min min interval) and retained for long online charts (multi-month window).
- Public chart ranges: 8h / 12h / 24h / 48h / 7d / 30d / 1y — see /time/old/.
- Host health / preference watchdog also runs on the 10‑minute cron (tunnel preference flips, workstation timeout, presence maintenance).
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
| Endpoint | Purpose |
|---|---|
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
/time— Root-Times snapshot: server clock, McDay id, phase, ticks, time until next midnight, your playtime./times— staff-oriented McDay dump (length, zone, enabled)./timezone— set your activity band (feeds pies + Discord TZ roles)./playtime— lifetime + per-bucket breakdown (Towny / Claims / total).
Want charts? Open the live time metrics dashboard · Host health · Leaderboards · Constitution (governance voting also weights playtime).