refactor(world): own live environment state

Move the DAT sky, selected day group, world clock, weather, AdminEnvirons bridge, and debug cycles into a one-shot WorldEnvironmentController while preserving GameWindow's public aliases and accepted startup/session/render order. Correct the named retail citations and register the remaining environment audio and fog/radar gaps.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 10:54:33 +02:00
parent 557eb7ef6b
commit d09e246d3a
19 changed files with 606 additions and 368 deletions

View file

@ -61,8 +61,9 @@ public static class DerethDateTime
/// Base/anchor year for the Portal Year calendar (retail
/// <c>GameTime.ZeroYear</c> = 10). Tick 0 corresponds to PY 10,
/// Morningthaw 1, Morntide-and-Half. Retail's <c>TimeOfDay+0x64</c>
/// ("absolute year") includes this offset, and <c>SkyDesc::PickCurrentDayGroup</c>
/// (<c>FUN_00501990</c>) feeds the absolute year into its LCG seed.
/// ("absolute year") includes this offset, and
/// <c>SkyDesc::CalcPresentDayGroup @ 0x00500E10</c> feeds the absolute year
/// into its LCG seed (older-build cross-reference <c>FUN_00501990</c>).
/// </summary>
public const int ZeroYear = 10;
@ -244,7 +245,8 @@ public static class DerethDateTime
/// Matches retail's <c>TimeOfDay + 0x64</c> field
/// (<c>FUN_005a7510:5300</c>:
/// <c>floor((worldTime+base)/secsPerYear) + baseYear</c>). This is
/// the value the retail DayGroup picker (<c>FUN_00501990</c>) feeds
/// the value the retail DayGroup picker
/// (<c>SkyDesc::CalcPresentDayGroup @ 0x00500E10</c>) feeds
/// into its LCG seed, so acdream must match for identical weather
/// picks vs retail.
/// </summary>
@ -256,7 +258,7 @@ public static class DerethDateTime
/// (dat's ZeroTimeOfYear) to match retail's <c>TimeOfDay + 0x68</c>
/// field (<c>FUN_005a7510:5304</c>:
/// <c>floor(withinYearSec / secsPerDay)</c>). Consumed by
/// <c>SkyDesc.PickCurrentDayGroup</c> as part of the per-day seed.
/// <c>SkyDesc::CalcPresentDayGroup</c> as part of the per-day seed.
/// </summary>
public static int DayOfYear(double ticks)
{

View file

@ -186,8 +186,9 @@ public sealed class LoadedSkyDesc
/// <summary>
/// Pick a <see cref="DayGroupData"/> deterministically for the given
/// Derethian (year, dayOfYear) pair. Retail-verbatim port of
/// <c>SkyDesc::PickCurrentDayGroup</c> (<c>FUN_00501990</c> at
/// <c>chunk_00500000.c:1276</c>) — the per-frame weather roller.
/// <c>SkyDesc::CalcPresentDayGroup @ 0x00500E10</c>. The older-build
/// fallback is <c>FUN_00501990</c> at
/// <c>chunk_00500000.c:1276</c>.
///
/// <para>
/// <b>Algorithm</b> (from the retail decompile):
@ -206,14 +207,10 @@ public sealed class LoadedSkyDesc
/// </para>
///
/// <para>
/// <paramref name="secondsPerDay"/> should be the dat-declared
/// "seconds per Derethian day" integer (retail reads it from
/// <c>TimeOfDay + 0x10</c>). acdream's callers pass
/// <see cref="DerethDateTime.DayTicks"/> as an int (7620); ACE
/// computes the same value server-side so retail and acdream
/// converge on identical picks whenever their <c>(Year, DayOfYear)</c>
/// agree — which they do, because both derive from the server's
/// <c>PortalYearTicks</c>.
/// Despite its historical parameter name, <paramref name="secondsPerDay"/>
/// is the integer at retail <c>TimeOfDay + 0x10</c>. A live retail probe
/// established that this is Dereth's days-per-year value (360), which
/// <c>WorldEnvironmentController</c> supplies from the calendar constants.
/// </para>
///
/// <para>
@ -237,7 +234,7 @@ public sealed class LoadedSkyDesc
if (DayGroups.Count == 1) return 0;
// --- Retail FUN_00501990 line-by-line port ---
// --- Retail SkyDesc::CalcPresentDayGroup @ 0x00500E10 ---
// Step 1: deterministic per-day seed.
int seed = unchecked(year * secondsPerDay + dayOfYear);
@ -280,8 +277,8 @@ public sealed class LoadedSkyDesc
int dayOfYear = DerethDateTime.DayOfYear(serverTicks);
// Retail's TimeOfDay+0x10 is actually DaysPerYear (= 360 for Dereth,
// live probe 2026-04-23), NOT SecondsPerDay as the decompile agent
// mis-labeled. See GameWindow.RefreshSkyForCurrentDay for the full
// citation.
// mis-labeled. See WorldEnvironmentController.RefreshSkyForCurrentDay
// for the owning call site.
int secondsPerDay = DerethDateTime.DaysInAMonth * DerethDateTime.MonthsInAYear; // 360
int idx = SelectDayGroupIndex(absYear, secondsPerDay, dayOfYear);
return idx < DayGroups.Count ? DayGroups[idx] : null;

View file

@ -157,7 +157,7 @@ public sealed class WeatherSystem
/// <summary>
/// Drive the weather kind from the active retail DayGroup name
/// (see <c>SkyDesc::PickCurrentDayGroup</c> port at
/// (see <c>SkyDesc::CalcPresentDayGroup @ 0x00500E10</c> port at
/// <c>LoadedSkyDesc.SelectDayGroupIndex</c>). Retail has ONE source
/// of truth for weather — the DayGroup roll — so this replaces the
/// internal <see cref="RollKind"/> hash once the real DayGroup picker