sky(phase-3f): anchor calendar to dat's GameTime.ZeroTimeOfYear
Final piece of the retail-sync puzzle. Live Dereth dat has GameTime.ZeroTimeOfYear = 3600 (verified 2026-04-23 diagnostic dump). Our DerethDateTime hardcoded +7/16 × DayTicks = 3333.75, copied from ACE's DerethDateTime.cs comment "tick 0 = Morntide-and-Half". The dat is authoritative; ACE's comment is wrong by 266.25 ticks (~33 Dereth minutes). User-observed regression (2026-04-23): acdream: middle-of-night (Darktide), clear, DayGroup "Sunny" retail: near-pre-dawn (Foredawn), thunderstorm, stormy DayGroup (both connected to the same ACE at PortalYearTicks=291134079) Same server tick → different calendar extraction → the offset skewed dayFraction AND pushed DayOfYear across a boundary at certain ticks, feeding a different LCG seed into the DayGroup picker (FUN_00501990). A single 266.25-tick offset error explains both the time mismatch and the weather mismatch. Code changes: - DerethDateTime.OriginOffsetTicks — runtime-settable static, default = DayFractionOriginOffsetTicks (3333.75, the legacy fallback). Applied in DayFraction, Year, DayOfYear, ToCalendar. - DerethDateTime.SetOriginOffsetFromDat(double) — called at Region load. - SkyDescLoader.DumpRegionSkyDesc dumps GameTime fields (and all 16 TimesOfDay entries) when ACDREAM_DUMP_SKY=1. - GameWindow.LoadRegion adopts the dat's ZeroTimeOfYear after LoadFromRegion, logs the before/after values. Also dumps every Dereth TimeOfDay hour-boundary (0..15) so any future calendar weirdness has authoritative ground truth in the log. Build + 733 tests green (no test depended on the hardcoded offset). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5f9df4d620
commit
cd8a37a9c8
3 changed files with 110 additions and 31 deletions
|
|
@ -393,6 +393,40 @@ public static class SkyDescLoader
|
|||
|
||||
Console.WriteLine("[sky-dump] ======== BEGIN SkyDesc dump ========");
|
||||
Console.WriteLine($"[sky-dump] Region Id={region.Id:X8} Number={region.RegionNumber} Name=\"{region.RegionName}\"");
|
||||
|
||||
// Phase 3f diag — retail TimeOfDay::OnTick uses
|
||||
// GameTime.ZeroTimeOfYear as an additive tick offset before
|
||||
// calendar extraction. Our DerethDateTime currently hardcodes
|
||||
// +7/16 (= 3333.75) for the dayFraction math and IGNORES this
|
||||
// field. If the dat value is anything other than what we assume,
|
||||
// our calendar is skewed from retail's at every moment.
|
||||
var gt = region.GameTime;
|
||||
if (gt is not null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[sky-dump] GameTime ZeroTimeOfYear={gt.ZeroTimeOfYear} ZeroYear={gt.ZeroYear} " +
|
||||
$"DayLength={gt.DayLength} DaysPerYear={gt.DaysPerYear} " +
|
||||
$"YearSpec=\"{gt.YearSpec}\" TimesOfDay.Count={gt.TimesOfDay?.Count ?? 0} " +
|
||||
$"DaysOfWeek.Count={gt.DaysOfWeek?.Count ?? 0} Seasons.Count={gt.Seasons?.Count ?? 0}");
|
||||
|
||||
// Dump every TimeOfDay slot — this is the retail-authoritative
|
||||
// hour boundary table. Anchors our offset math: if retail
|
||||
// TimeOfDay[0].Start != our assumed 0, we have our answer.
|
||||
if (gt.TimesOfDay is not null)
|
||||
{
|
||||
for (int i = 0; i < gt.TimesOfDay.Count; i++)
|
||||
{
|
||||
var t = gt.TimesOfDay[i];
|
||||
Console.WriteLine(
|
||||
$"[sky-dump] TimeOfDay[{i}] Start={t.Start} IsNight={t.IsNight} Name=\"{t.Name}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[sky-dump] GameTime: null (no calendar info in this region)");
|
||||
}
|
||||
|
||||
Console.WriteLine($"[sky-dump] SkyDesc TickSize={sky.TickSize} LightTickSize={sky.LightTickSize} DayGroups.Count={sky.DayGroups.Count}");
|
||||
|
||||
for (int g = 0; g < sky.DayGroups.Count; g++)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue