# Retail acclient.exe Live Memory Probe — TimeOfDay Struct **Date:** 2026-04-23 **Tool:** `tools/RetailTimeProbe/` — P/Invoke `ReadProcessMemory` against a running retail client **Target:** `C:\Turbine\Asheron's Call\acclient.exe`, pid 17980 ## Observed values ``` module base = 0x00400000 (no ASLR delta from preferred) DAT_008ee9c8 relocated to 0x008EE9C8 TimeOfDay* = 0x00A5C1C0 EpochBase (+0x00 double) = 3600.000000 ← matches dat ZeroTimeOfYear ✓ BaseYear (+0x08 int) = 10 ← matches dat ZeroYear ✓ SecondsPerDay (+0x0C float) = 7620 ← matches dat DayLength ✓ +0x10 (int) = 360 ← see §1 — NOT seconds-per-day! SecondsPerYear (+0x40 double) = 2743200.000000 ← DayLength × DaysPerYear ✓ DayFraction (+0x48 float) = 0.340492 ← see §2 CurDayStart (+0x50 double) = 291133740.000000 CurDayEnd (+0x58 double) = 291136597.500000 Year (+0x64 int) = 116 ← matches our AbsoluteYear ✓ DayOfYear (+0x68 int) = 47 ← matches our DayOfYear ✓ SeasonIndex (+0x6C int) = 1 ``` Acdream at the same wall-clock moment: ``` AbsoluteYear = 116 ✓ DayOfYear = 47 ✓ ``` ## 1. The `+0x10` field is DaysPerYear (360), not SecondsPerDay The decompile agent's C trail (`docs/research/2026-04-23-sky-decompile-hunt-C.md` §1 + §4) labeled `TimeOfDay+0x10` as *"SecondsPerDay (int copy — source of iVar6)"* for the `FUN_00501990` (`SkyDesc::PickCurrentDayGroup`) LCG seed. The live probe disproves that. The value is **360** — the same as `GameTime.DaysPerYear` from the dat. Implication for the retail LCG seed formula: ```c // FUN_00501990 line 1296 of chunk_00500000.c: iVar4 = (iVar3 * iVar6 + iVar4) * 0x6a42fdb2 + -0x7541e9ae; // ^Year ^x0x10 ^DayOfYear ``` With `iVar6 = 360` (DaysPerYear), the seed is: ``` seed = Year × DaysPerYear + DayOfYear = total days since (Year 0, DayOfYear 0) ``` That's a flat day-index, which makes obvious sense for a per-day weather picker. Retail's engineers didn't pass a "seconds per day" at all — they passed "days per year" as the year-to-day multiplier so the final seed is a total-day count. **Actionable:** our C# port must pass 360 (`DaysInAMonth * MonthsInAYear`) as the LCG's `secondsPerDay` parameter, not the 7620 we previously used. With 7620 we compute `seed = 883,967`; retail computes `seed = 41,807`. Completely different LCG outputs → different DayGroup picks → weather mismatch users observed against their retail client side-by-side. ## 2. `+0x48 DayFraction` is a sub-period fraction, not full-day Live retail at the probed moment: - Calendar: `PY116 ColdMeet 18 Dawnsong` (hour 4 of 16, i.e. morning). - DayFraction: 0.340492 Our acdream at the same tick would compute `dayFraction ≈ 0.13` using the full-day formula `((tick + ZeroTimeOfYear) mod DayLength) / DayLength`. The stored `CurDayStart/End` bounds give it away: ``` CurDayEnd - CurDayStart = 2857.5 ticks = 0.375 × 7620 = 6 Dereth hours ``` 6 hours = night duration (hours 0-3 + 14-15 = 6 hours of the 16-hour day). So retail's `+0x48 DayFraction` is **"fraction through current day/night period"**, NOT "fraction through full calendar day". Using these fields: ``` retailDayFraction = (currentTick - CurDayStart) / (CurDayEnd - CurDayStart) ``` For our use-case (bracketing SkyTime keyframes whose `Begin` is on a [0, 1] full-day scale), the full-day formula we have is correct — sky keyframes don't care about retail's internal sub-period storage. The difference is only visible if we reverse-engineer "what hour does retail think it is" by reading its field; we wouldn't use retail's `+0x48` value for interpolation even if we could. So: no fix required for our dayFraction path. The keyframe bracket works correctly with our own `(tick + 3600) mod 7620 / 7620` formula. ## 3. Confirmed no-change items - `EpochBase` / `ZeroTimeOfYear` = 3600 ✓ (Phase 3f commit cd8a37a already adopted this from `Region.GameTime.ZeroTimeOfYear`). - `BaseYear` / `ZeroYear` = 10 ✓ (`DerethDateTime.ZeroYear`). - `SecondsPerDay` float (+0x0C) = 7620 ✓ (`DayTicks`). - `SecondsPerYear` = 2,743,200 ✓ (`YearTicks`). - `Year` = 116, `DayOfYear` = 47 — our `AbsoluteYear` and `DayOfYear` helpers already reproduce these from the server tick. ## 4. Follow-ups None critical after the Phase 3g fix (DaysPerYear in LCG). If the DayFraction sub-period storage becomes relevant (e.g. if we ever render retail's "Xm until night" UI), we'd replicate the CurDayStart/End computation from `FUN_005a7800` in the decompile.