# Sky DAT Schema — Full Map **Date:** 2026-04-23 **Scope:** Every field in the retail Region→SkyDesc→DayGroup→SkyObject/SkyTimeOfDay tree, with units, comments, and cross-references. Covers SurfaceType flags and the acdream interpretation. **Purpose:** Pre-port audit. No code changes. **Sources:** DatReaderWriter auto-generated Ghidra-parsed types; ACE; ACViewer; WorldBuilder. Retail decompile (`docs/research/decompiled/chunk_*.c`) has NO sky-related matches (confirmed in prior audit, `2026-04-21-sky-deep-audit.md:9-15`). --- ## 1. Structure Tree ``` Region (DB_TYPE_REGION, 0x13000000–0x1300FFFF) ├── RegionNumber : uint32 // internal ID ├── Version : uint32 ├── RegionName : PString ├── LandDefs, GameTime, PartsMask, SoundInfo, SceneInfo, TerrainInfo, RegionMisc └── SkyInfo : SkyDesc (only if PartsMask has HasSkyInfo) SkyDesc ├── TickSize : double // seconds per game-tick (day length unit) ├── LightTickSize : double // seconds per lighting update tick └── DayGroups : List DayGroup ├── ChanceOfOccur : float // weight in [0, 1] for PDF rolling ├── DayName : PString // e.g. "Clear", "Sunny" — internal-only label ├── SkyObjects : List // celestial meshes (sun/moon/clouds/...) └── SkyTime : List // keyframes through the day SkyObject // ONE celestial layer ├── BeginTime : float // [0, 1] day-fraction visibility start ├── EndTime : float // [0, 1] day-fraction visibility end; wraps if End // the mesh (0 = hidden) ├── DefaultPesObjectId: QualifiedDataId // optional particle emitter └── Properties : uint32 // flag bits (billboard? follow-camera? — not decoded) SkyTimeOfDay // ONE keyframe at time T ├── Begin : float // [0, 1] when this keyframe becomes the "active" interpolant ├── DirBright : float // sun intensity multiplier (unit unspecified; typically 0..~1.5) ├── DirHeading : float // sun compass heading (degrees; 0=N, 90=E, …) ├── DirPitch : float // sun elevation above horizon (degrees; -90..+90) ├── DirColor : ColorARGB // sun RGB, bytes 0..255 each ├── AmbBright : float // ambient intensity multiplier ├── AmbColor : ColorARGB // ambient RGB, bytes 0..255 each ├── MinWorldFog : float // meters — fog starts ├── MaxWorldFog : float // meters — fog saturates ├── WorldFogColor : ColorARGB // fog tint, bytes 0..255 each ├── WorldFog : uint32 // mode enum: 0=off, 1=D3DFOG_LINEAR, 2=exp, 3=exp2 └── SkyObjReplace : List SkyObjectReplace // per-keyframe override of one SkyObject ├── ObjectIndex : uint32 // index into owning DayGroup.SkyObjects ├── GfxObjId : QualifiedDataId // mesh override (0 = keep default) ├── Rotate : float // heading override — **degrees** ├── Transparent : float // UNIT DISPUTED — see §4 ├── Luminosity : float // UNIT DISPUTED — see §4 └── MaxBright : float // UNIT DISPUTED — see §4 ``` File citations: `references/DatReaderWriter/DatReaderWriter/Generated/DBObjs/Region.generated.cs:27-67`, `Types/SkyDesc.generated.cs:23-30`, `Types/DayGroup.generated.cs:23-31`, `Types/SkyObject.generated.cs:23-41`, `Types/SkyTimeOfDay.generated.cs:23-47`, `Types/SkyObjectReplace.generated.cs:23-35`. --- ## 2. Field Table with Units & Sources | Field | Type | Unit / Range | Comment source | Example | |---|---|---|---|---| | `SkyDesc.TickSize` | double | seconds? | none in generator | 1.0 in test fixture (`tests/AcDream.Core.Tests/World/SkyDescLoaderTests.cs:26`) | | `SkyDesc.LightTickSize` | double | seconds? | none | 2.0 in test fixture (same) | | `DayGroup.ChanceOfOccur` | float | probability weight [0,1]? | none; r12 §11 describes PDF use (`docs/research/deepdives/r12-weather-daynight.md:544`) | 1.0 | | `DayGroup.DayName` | PString | UTF-8 | none | "Sunny" (memory; diag dump referenced in `SkyDescLoader.cs:253`) | | `SkyObject.BeginTime` | float | day-fraction [0,1] | r12 deepdive §2 "normalized day-time" (`deepdives/r12-weather-daynight.md:161`) | 0.0 (always-visible dome) | | `SkyObject.EndTime` | float | day-fraction [0,1]; Begin==End → always visible; Begin>End → wraps midnight | our `SkyDescLoader.cs:46-52` encodes this; matches WorldBuilder `SkyboxRenderManager.cs:188-197` | 1.0, 0.25 | | `SkyObject.BeginAngle` | float | degrees | r12 §2 "where on the sky arc" | 0° | | `SkyObject.EndAngle` | float | degrees | r12 §2 lerped linearly | 360° | | `SkyObject.TexVelocityX` | float | UV/second | r12 §2.1 "UV scroll rate — cloud drift, star twinkle" | ~0.001 typical | | `SkyObject.TexVelocityY` | float | UV/second | same | ~0.001 | | `SkyObject.DefaultGfxObjectId` | QualifiedDataId | dat-ID; 0=hidden | none | 0x01000XYZ typical | | `SkyObject.DefaultPesObjectId` | QualifiedDataId | dat-ID; 0=none | r12 §2.1 "unused at top" | 0 | | `SkyObject.Properties` | uint32 | flag bits | not decoded in references; our code ignores | 0 | | `SkyTimeOfDay.Begin` | float | day-fraction [0,1] | our `SkyState.cs:45` "day-fraction this keyframe kicks in" | 0.0, 0.25, 0.5, 0.75 typical | | `SkyTimeOfDay.DirBright` | float | multiplier, **> 1 legal** | our `SkyState.cs:40-42` "channels above 1.0 and the frag shader clamps" | 1.5 in test | | `SkyTimeOfDay.DirHeading` | float | compass degrees, 0=N clockwise | r12 §3 (`deepdives/r12-weather-daynight.md:262`) | 180° at noon | | `SkyTimeOfDay.DirPitch` | float | degrees, -90..+90 from horizontal | r12 §3 | 70° at noon | | `SkyTimeOfDay.DirColor` | ColorARGB | 4×byte BGRA-packed in file | `ColorARGB.generated.cs:27-45` "0-255" | {R=255,G=250,B=220} at noon | | `SkyTimeOfDay.AmbBright` | float | multiplier | none; same semantics as DirBright | 0.4 in test | | `SkyTimeOfDay.AmbColor` | ColorARGB | 4×byte BGRA | same | noon ~(82,82,89) | | `SkyTimeOfDay.MinWorldFog` | float | meters | r12 §5.1 (`deepdives/r12-weather-daynight.md:346`) | 120 | | `SkyTimeOfDay.MaxWorldFog` | float | meters | r12 §5.1 | 350 | | `SkyTimeOfDay.WorldFogColor` | ColorARGB | 4×byte BGRA | none | similar to horizon band | | `SkyTimeOfDay.WorldFog` | uint32 | D3D fog mode: 0 off, 1 linear, 2 exp, 3 exp2 | r12 §5.1 "D3DFOG_LINEAR, etc" (line 349) | 1 (linear) | | `SkyObjectReplace.ObjectIndex` | uint32 | index into DayGroup.SkyObjects | none | 0..6 | | `SkyObjectReplace.GfxObjId` | QualifiedDataId | dat ID; 0 = keep default | r12 §2.3 "swap mesh" | 0x01000... or 0 | | `SkyObjectReplace.Rotate` | float | **degrees** heading override | tested in `SkyDescLoader.cs:260-263`: "270° values in the data are genuinely heading-degrees, not percentages" | 270 observed | | `SkyObjectReplace.Transparent` | float | **UNIT UNCONFIRMED** — see §4 | none | values up to 100 observed in acdream diag dump | | `SkyObjectReplace.Luminosity` | float | **UNIT UNCONFIRMED** — see §4 | none | values up to 100 observed | | `SkyObjectReplace.MaxBright` | float | **UNIT UNCONFIRMED** — see §4 | none | values up to 100 observed | Note on `ColorARGB`: the wire/byte order is B, G, R, A per `ColorARGB.generated.cs:49-52`; the member names are semantic (Red=red channel 0..255 regardless of file byte position). Our loader's `ColorToVec3` at `SkyDescLoader.cs:311-315` uses the semantic `.Red/.Green/.Blue / 255f` which is correct. --- ## 3. SurfaceType Enum (full) From `references/DatReaderWriter/DatReaderWriter/Generated/Enums/SurfaceType.generated.cs:13-41` (cross-checked `dats.xml:744-758`). Flags, parent uint: | Name | Value | Notes / inferred meaning | |---|---|---| | `Base1Solid` | `0x00000001` | Surface is a flat color (no texture); `Surface.ColorValue` field populated | | `Base1Image` | `0x00000002` | Surface has a texture (SurfaceTexture DataId) | | `Base1ClipMap` | `0x00000004` | Alpha-keyed texture; fragment shader discards low-alpha pixels | | `Translucent` | `0x00000010` | Implies standard alpha blending | | `Diffuse` | `0x00000020` | Receives diffuse lighting (default state for most surfaces) | | `Luminous` | `0x00000040` | Self-illuminated / unshaded — NOT additive blend; see §5 | | `Alpha` | `0x00000100` | Explicit alpha blend | | `InvAlpha` | `0x00000200` | Inverted alpha blend (rare) | | `Additive` | `0x00010000` | Additive blend (sun, glow, particle) | | `Detail` | `0x00020000` | Detail texture layer | | `Gouraud` | `0x10000000` | Gouraud shading (vs. flat) | | `Stippled` | `0x40000000` | Stipple pattern (AC-era hardware dithering) | | `Perspective` | `0x80000000` | Perspective-correct texturing | Our `TranslucencyKind.FromSurfaceType` at `src/AcDream.Core/Meshing/TranslucencyKind.cs:61-74` maps these to blend modes: - `Additive` (0x10000) → `TranslucencyKind.Additive` (src,one blend) - `InvAlpha` (0x200) → `TranslucencyKind.InvAlpha` - `Alpha | Translucent` (0x100 | 0x10) → `TranslucencyKind.AlphaBlend` (src_alpha, one_minus_src_alpha) - `Base1ClipMap` (0x04) → `TranslucencyKind.ClipMap` (opaque draw, fragment discard) - else → `Opaque` `Luminous` (0x40) is **not** mapped to any translucency category — it's a lighting hint, not a blend mode. The code comment at `SkyRenderer.cs:345-350` documents a past bug where we treated Luminous as additive and "blew the whole sky to white." That call is consistent with ACE/ACViewer usage (Luminous sets `OrigLuminosity` on the `PhysicsPart.Surface`; see `references/ACViewer/ACViewer/Physics/Common/Surface.cs:15-19`). No generator comments in Chorizite.ACProtocol (it has no dat-surface types — it's network-message-only). No explicit "SrcBlend/DestBlend" comments anywhere in the references. --- ## 4. acdream SkyDescLoader vs Dat Reality — What's Likely Right / Wrong ### Likely correct - **`ColorARGB` → `Vector3/255f`** (`SkyDescLoader.cs:311-315`) — matches the generator comment "0-255" on each channel. - **Sun/Amb color pre-multiplied by bright** (`SkyDescLoader.cs:289-291`) — matches r12 §4 formula (`deepdives/r12-weather-daynight.md:307-309`); test pins this (`SkyDescLoaderTests.cs:84`). - **`WorldFog` enum mapping** (`SkyDescLoader.cs:278-284`) — matches r12 §5.1 (D3DFOG_LINEAR = 1, exp = 2, exp2 = 3). - **Visibility / arc-progress wrap logic** (`SkyDescLoader.cs:46-76`) — matches WorldBuilder `SkyboxRenderManager.cs:188-240` line-by-line. - **`Rotate` kept as degrees** (`SkyDescLoader.cs:260`) — confirmed by our own observation that diag values of 270 are headings. ### Likely WRONG: the `/100` on Transparent / Luminosity / MaxBright **This is the load-bearing speculative decision.** Code: `SkyDescLoader.cs:273-275` divides all three by 100. The comment at 249-267 claims "confirmed from live diag dump … have Luminosity=100 and Transparent=100" — but there is NO supporting evidence anywhere else in the references: 1. **DatReaderWriter generator has no comment** on those three fields (`SkyObjectReplace.generated.cs:28-34`). Unlike `Surface.Luminosity` which is documented as "Self-illumination / emissive strength" and used as a **0..1 fraction** in the test fixture (`SurfaceTests.cs:27`, value `0.3f`), the `SkyObjectReplace` fields have no generator comment. 2. **ACE's `SkyObjectReplace.cs:10-12`** defines them as `float` with no unit comment. 3. **ACViewer** just displays them raw as `"Transparent: {value}"` without transform (`references/ACViewer/ACViewer/Entity/SkyObjectReplace.cs:35-48`). 4. **WorldBuilder doesn't use them at all** — `SkyboxRenderManager.cs:180-264` ignores `Transparent/Luminosity/MaxBright` entirely. So WorldBuilder gives us zero evidence either way about units. 5. **No decompile data**: `docs/research/decompiled/chunk_*.c` has no matches for sky/SkyObject/Luminosity (confirmed in the prior audit `2026-04-21-sky-deep-audit.md:9-15`). 6. **Our existing Surface.Luminosity uses a fraction** (test shows 0.3f), so there's a strong cross-field analogy that `SkyObjectReplace.Luminosity` is also a fraction, not a percentage. 7. **Our own commit `eeae83a` added the /100 based on a live diag dump** — but we don't have the raw dump file in `docs/research/` and the prior audit `2026-04-22-sky-lighting-decompile.md:130-135` observed "Diag shows luminosity values max at 0.78 (not > 1)" — which is already in the 0..1 range **before** any /100 transform. **Status: SPECULATIVE.** Two conflicting claims in our own codebase: - `SkyDescLoader.cs:253-257`: "noon keyframes have Luminosity=100 and Transparent=100" (said to justify /100). - Prior audit `2026-04-22-sky-lighting-decompile.md:143`: "luminosity values max at 0.78". These cannot both be true for the same dat. At least one of those observation logs is incorrect — or they were looking at different fields. **Recommendation for the next decompile pass:** capture a fresh raw-bytes dump of one DayGroup's SkyTimeOfDay.SkyObjReplace list from the live Dereth dat (region 0x13000000), print as hex + parsed float, and pin the unit empirically. Until then, treat the `/100` as "best guess based on one diag observation" rather than a confirmed fact. ### Fields we IGNORE that the dat has - `SkyObject.Properties` (uint32 flag bits) — we read it into `SkyObjectData.Properties` but never consult it. Retail-faithful rendering probably reads one of these bits for billboard-vs-mesh orientation. - `SkyObject.DefaultPesObjectId` — we never load the PhysicsScript; retail probably attaches it as a rain/snow particle emitter (r12 §6.1, `deepdives/r12-weather-daynight.md:423-426`). - `SkyTimeOfDay.WorldFog` (currently mapped to `FogMode` but unused past loading — we don't switch between linear/exp/exp2 per keyframe in our shader). - `SkyDesc.TickSize` / `LightTickSize` — loaded but not consulted; retail uses these for the lighting-update quantisation rate. --- ## 5. The 7 Sky Objects (Dereth DayGroup) The `SkyDescLoader.cs` code comment asserts there are 7 sky objects per DayGroup. r12 deepdive (`deepdives/r12-weather-daynight.md:235-237`) says "roughly 4–6 sky objects (one background, one cloud sheet, one sun, one moon, one star sheet)". ### Without a fresh diag dump, we cannot positively assign GfxObjId → role. What we can say from the primary-day-group pattern and the pipeline: | Index | Expected role | BeginTime/EndTime pattern | Mesh shape | Blend | |---|---|---|---|---| | 0 | Sky dome / gradient background | Begin==End (always visible) | Large hemisphere with baked gradient in V | AlphaBlend or Opaque | | 1 | Cloud layer (upper) | Begin==End (always) | Large hemisphere with cloud texture, UV-scrolled | AlphaBlend | | 2 | Cloud layer (lower) | Begin==End (always) | Same shape, different texture | AlphaBlend | | 3 | Sun | BeginTime ≈ 0.25, EndTime ≈ 0.75 (day) | Small billboard quad with bright body | Additive | | 4 | Moon | Wraps midnight (Begin > End) | Small billboard quad | Additive | | 5 | Star dome | Wraps midnight (Begin > End) | Large hemisphere with star texture | Additive | | 6 | Secondary moon or decoration | varies | billboard | Additive or AlphaBlend | **This is the expected pattern from r12, NOT a verified per-index dump.** Verifying requires running the client with `ACDREAM_DUMP_SKY=1` or equivalent and logging every SkyObject. **Action to confirm in a future pass**: write a one-shot CLI or log-dump in `SkyDescLoader.LoadFromRegion` that prints `[i] GfxObjId=0x… BeginTime=… EndTime=… BeginAngle=… EndAngle=… Properties=…` for every sky object in each DayGroup, then capture the output. File it under `docs/research/` as the "Dereth sky object table" so future work has ground truth. ### What the prior audit found but was lost to memory `2026-04-21-sky-deep-audit.md:128-131` describes a white-sky bug and documents 7 objects but does NOT list their GfxObjIds. The comment at `SkyRenderer.cs:214-218` captures typical ambient colors per keyframe (noon, dusk, midnight, dawn) but says nothing specific about which GfxObj is which role. --- ## 6. Open Questions / Verify Next 1. **Unit of Transparent/Luminosity/MaxBright on SkyObjectReplace.** Strongest evidence (cross-reference to Surface.Luminosity and ACE/ACViewer silence) suggests it's a 0..1 fraction and our `/100` is wrong. Verify with fresh raw-bytes dump. 2. **What is `SkyObject.Properties`?** Retail probably reads a "billboard" bit here. Decompile needed. 3. **Per-SkyObject blend mode — do we classify it correctly?** Current code uses `sm.Translucency == TranslucencyKind.Additive` but only reads the `Additive` (0x10000) flag. If the dat ALSO uses `Luminous` as an additive cue in conjunction with `Alpha` (a combined "luminous alpha mesh"), we'd miss it. Needs a dat dump of every SkyObject → GfxObj → Surface.Type for the 7 Dereth sky objects. 4. **`SkyDesc.TickSize` units.** r12 says day length is 7620 game-ticks. If `TickSize` is seconds-per-tick, it gives total day seconds. Need to verify by reading the value out of the live dat. 5. **`WorldFog` mode — linear only, or do keyframes actually switch to exp?** Our FogMode mapping accepts 1/2/3 but the shader only implements linear. If any retail keyframe uses 2 or 3, we render wrong fog falloff. --- ## 7. References - DatReaderWriter sky types: `references/DatReaderWriter/DatReaderWriter/Generated/Types/{SkyDesc,SkyObject,SkyTimeOfDay,SkyObjectReplace,DayGroup}.generated.cs` - DatReaderWriter Region: `references/DatReaderWriter/DatReaderWriter/Generated/DBObjs/Region.generated.cs:27-120` - DatReaderWriter Surface: `references/DatReaderWriter/DatReaderWriter/Generated/DBObjs/Surface.generated.cs:27-101` - DatReaderWriter SurfaceType: `references/DatReaderWriter/DatReaderWriter/Generated/Enums/SurfaceType.generated.cs:13-41` - DatReaderWriter Polygon: `references/DatReaderWriter/DatReaderWriter/Generated/Types/Polygon.generated.cs:23-87` - DatReaderWriter ColorARGB: `references/DatReaderWriter/DatReaderWriter/Generated/Types/ColorARGB.generated.cs:22-65` - DatReaderWriter Surface test fixture: `references/DatReaderWriter/DatReaderWriter.Tests/DBObjs/SurfaceTests.cs:21-45` - ACE SkyObjectReplace: `references/ACE/Source/ACE.DatLoader/Entity/SkyObjectReplace.cs:5-24` - ACE Physics Surface: `references/ACE/Source/ACE.Server/Physics/Common/Surface.cs:5-35` - ACViewer SkyObjectReplace tree: `references/ACViewer/ACViewer/Entity/SkyObjectReplace.cs:14-52` - WorldBuilder SkyboxRenderManager: `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/SkyboxRenderManager.cs:180-274` - acdream loader: `src/AcDream.Core/World/SkyDescLoader.cs` - acdream renderer: `src/AcDream.App/Rendering/Sky/SkyRenderer.cs` - acdream translucency classifier: `src/AcDream.Core/Meshing/TranslucencyKind.cs:59-74` - r12 deep dive: `docs/research/deepdives/r12-weather-daynight.md:135-240, 290-330, 340-400, 430-475` - Prior audits: `docs/research/2026-04-21-sky-deep-audit.md`, `docs/research/2026-04-22-sky-lighting-decompile.md` - dats.xml (protocol definition): `references/DatReaderWriter/DatReaderWriter/dats.xml:744-758`