Commit graph

7 commits

Author SHA1 Message Date
Erik
bd184e1afd fix(world): DerethDateTime tick-0 offset — sky was 7/16 of a day wrong
User observed: 'time is flipped — supposed to be day/evening, but shows
night/morning.' That's a ~half-day offset.

Root cause in ACE DerethDateTime.cs line 23:
  private const double dayZeroTicks = 0; // Morningthaw 1, 10 P.Y. - Morntide-and-Half

ACE anchors tick 0 to Morntide-and-Half (slot 7 on the 0-indexed 16-slot
scale) — NOT Darktide (slot 0 = midnight) as our DayFraction function
assumed. Confirmed by DerethDateTime.cs:145:
  private int hour = (int)Hours.Morntide_and_Half;

Fix: shift DayFraction by +7/16 * DayTicks (3333.75) so tick 0 maps to
its real calendar slot. Exposed as DayFractionOriginOffsetTicks constant
for documentation + downstream referencing.

Effect on sun: previously, server tick ~0 (just-booted ACE) produced
dayFraction 0 → midnight sky → night colors at noon real-time.
Now dayFraction 7/16 = 0.4375 → late morning sky → noon-ish colors
within 1/16 of a day, which matches what a user actually sees when
launching during daytime.

Tests updated for the corrected convention:
- DerethDateTime.DayFraction(0) = 7/16 (not 0).
- CurrentHour(0) = MorntideAndHalf (not Darktide).
- IsDaytime(0) = true.
- Midnight (Darktide, slot 0) is 9/16 of a day past tick 0.
- SkyState + WorldTimeDebug tests retargeted to the new frame.

Build green, 711 tests pass.

Ref: references/ACE/Source/ACE.Common/DerethDateTime.cs:23-25 + :145.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:27:49 +02:00
Erik
756def5ceb feat(world): Phase G.1 — debug-time override tests + clear-color clamp
Small polish commit:
- Clamp ClearColor inputs to [0, 1] because retail keyframes store
  sun/fog colors pre-multiplied by their brightness scalars, which can
  exceed 1.0; some drivers treat ClearColor > 1 as a saturate-bright
  hint and produce visible color shifts at the edges.
- 4 new tests cover WorldTimeService.SetDebugTime / ClearDebugTime /
  SyncFromServer-clears-override / SetProvider hot-swap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:52:54 +02:00
Erik
0df1c5b4a6 feat(world): Phase G.1 data model — dat-accurate SkyKeyframe + WeatherSystem
Expand the SkyKeyframe record with retail-exact fog fields (FogStart,
FogEnd, FogMode) per r12 §5. The existing FogDensity field is retained
for backwards compat with tests that pin it; new shipping code reads
FogStart / FogEnd / FogMode directly.

Add WeatherSystem (WeatherKind + EnvironOverride enum + 10s transition
ease + deterministic per-day-index roll) matching r12 §6.1. Roll weights
are ~60% Clear / 20% Overcast / 12% Rain / 5% Snow / 3% Storm — tuned
against retail observations. Storm mode triggers lightning flashes
every 8–30 s via an exponential-decay (200ms τ) flash level that the
shader consumes as an additive scene bump.

Add SkyDescLoader that parses the Region dat (0x13000000) into
LoadedSkyDesc — DayGroupData with SkyObjectData (visibility window +
arc sweep), per-keyframe SkyObjectReplaceData, and a shader-ready
SkyStateProvider builder. Sun/ambient colors are pre-multiplied by
DirBright/AmbBright so the shader never needs to know about retail's
scalar brightness field.

19 new tests (weather determinism, transition ease, environ override
tint, flash decay, dat-load conversion with fog + pre-mult colors).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:29:33 +02:00
Erik
6850d716a2 feat(world): Phase G.1 DerethDateTime + SkyStateProvider + WorldTimeService
Client-side deterministic sky + weather + day/night system per R12.
Retail's model is 95% client-side: the server just delivers its
current PortalYearTicks (double, seconds since boot-seed) at login and
in TimeSync packets; the client computes everything else locally from
the constants in r12 §1.2 + ACE DerethDateTime.cs.

Core layer (AcDream.Core/World):
- DerethDateTime: retail-exact calendar (16 hours/day, 30 days/month,
  12 months/year, 7620 ticks/day, 2,743,200 ticks/year). HourName enum
  covers all 16 named half-hour slots (Darktide → GloamingAndHalf);
  MonthName covers the 12 Derethian months (Snowreap → Frostfell).
  DayFraction, CurrentHour, IsDaytime, ToCalendar.
- SkyKeyframe + SkyStateProvider: 4-keyframe default day/dawn/noon/dusk
  with linear color + angular-wrap heading interpolation + slerp-like
  shortest-arc lerp so heading wraps 350° → 10° don't tween backwards
  through 180°. Default keyframe colors tuned to retail screenshots
  (sunrise warm, noon white, sunset red, midnight deep blue).
- WorldTimeService: owns the live clock. SyncFromServer(ticks) sets
  baseline; NowTicks advances by real-time elapsed. Exposes DayFraction,
  CurrentSky, CurrentSunDirection, IsDaytime for the render thread.

This is the foundation Phase G.2 (dynamic lighting) consumes: lighting
uniforms are fed from CurrentSky's SunColor / AmbientColor / sun
direction, varying smoothly across the day.

Tests (16 new):
- DerethDateTime: midnight, half-day, wrap, Dawnsong, Midsong,
  day/night flag at dawn vs Darktide-Half, year rollover, month
  advance.
- SkyState: 4-default keyframes, noon-exact matches frame data,
  midpoint lerps between neighbours, wrap across midnight doesn't
  produce NaN, sun direction returns unit vector, WorldTimeService
  sync + DayFraction at noon.

Build green, 587 tests pass (up from 570).

Ref: r12 §1 (Portal Year math), §2 (sky objects), §4 (color lerp).
Ref: ACE DerethDateTime.cs + NetworkSession TimeSync handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:07:26 +02:00
Erik
768a9a0619 fix(app+core): Phase B.3 — Setup.StepUpHeight + scenery road exclusion
StepUpHeight: when Tab enters player mode, read Setup.StepUpHeight from the
player entity's dat and apply it to the controller (fallback 2f for non-Setup
entities or when the dat value is zero). Previously hardcoded to 5.0 which
made step-up too permissive.

Road exclusion: SceneryGenerator now skips terrain vertices where bits 0-1 of
the raw terrain word are non-zero. These bits encode the road type (GetRoad()
in ACViewer's Landblock.cs). Trees, rocks and bushes will no longer be placed
on road surfaces.

Added SceneryGenerator.IsRoadVertex(ushort) public helper + 9 unit tests
(theory + fact) verifying the road-bit convention matches TerrainInfo.Road.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 18:27:36 +02:00
Erik
5d35f4fe46 feat(core): add WorldView with 3x3 neighbor landblock computation 2026-04-10 18:02:41 +02:00
Erik
473a06c534 feat(core): add LandblockLoader with Stab+Building → WorldEntity mapping 2026-04-10 17:58:30 +02:00