ARE Format (Area Static Blueprint)
The Area (.are) blueprint format operates as the static environmental foundation of any game module. It establishes the rigid, overarching properties of a level, orchestrating the terrain’s grass rendering definitions, dynamic sunlight and fog constraints, ambient audio scale, and the primary interior/exterior state configurations. It effectively constructs the structural ‘stage’ that dynamic entities (like creatures and doors) populate later on.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .are |
| Magic Signature | ARE / V3.2 |
| Type | Area Static Blueprint |
| Rust Reference | View rakata_generics::Are in Rustdocs |
Data Model Structure
Rakata maps an Area into the rakata_generics::Are struct. The struct’s Rustdocs document every field’s binary schema and GFF mapping; the table below is the high-level anatomy.
| Category | Covers | Representative fields |
|---|---|---|
| Core Identity & State | The area’s tag, localized name, and interior/exterior state flags | Tag, Name, Flags, RestrictMode |
| Weather & Terrain | Rain, snow, and lightning chances, wind strength, and grass rendering | ChanceRain, WindPower, Grass_TexName |
| Lighting & Fog | Separate sun and moon ambient/diffuse tints, fog ranges, and shadow limits | SunAmbientColor, MoonFogNear, ShadowOpacity |
| Stealth XP | The stealth-run XP pool an area can award | StealthXPMax, StealthXPCurrent, StealthXPLoss |
| Event Hooks | The area-level event scripts | OnEnter, OnExit, OnHeartbeat, OnUserDefined |
| Map & Rooms | Minimap projection data and the per-room sound list | Map, Rooms |
| Minigame | The optional nested swoop or turret minigame configuration | MiniGame |
rakata-lint validates these fields against the engine constraints documented below.
Note
The ARE never gets re-saved – it gets re-copied. When a module is saved, the engine doesn’t parse or rewrite a single ARE field.
CSWSModule::SaveModuleFinishhands the area’s staticAREresource to a generic “copy this resource type verbatim” helper, which looks up whatever ARE the resource manager currently has bound for that area and copies it byte-for-byte into the save archive. Every engine rule and clamping behaviour documented on this page (weather truncation, fog clamping, tag lowercasing, and so on) applies only to the initial parse when a module is freshly loaded, never to the save/resume cycle.The area’s actual dynamic, session-changeable state – weather, stealth XP, map exploration, cameras, transition flags – lives entirely in the module’s GIT resource instead, not in a re-saved ARE. If you’re building a save-editing tool, patch the GIT; don’t expect to patch an ARE inside a save archive.
Engine Audits & Decompilation
The following documents the engine’s exact load sequence and field requirements for .are files mapped from swkotor.exe.
(Decompilation logic for this section was entirely audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from CSWSArea::LoadArea at 0x0050e190.)
The initial LoadArea dispatch branches out to parse the .are GFF, .lyt layout, .git instance tracking, and .pth bounds. The engine processes roughly 61 scalar fields, 4 scripts, 3 lists, and a nested minigame struct natively within the LoadAreaHeader subroutine.
Core Environmental Identity
| Field Category | Engine Property & Behavioral Quirk |
|---|---|
| Identity | Name (LocString), Comments (String), ID (Int) -> Standard definition strings. All three default unconditionally when absent: Name to an empty localized string, Comments to an empty string, ID to 0. Creator_ID (Int) and Version (DWord) share the same unconditional-0 default. |
| Identity | Tag (String) -> Lowercased on load (via CExoString::LowerCase). The only tag to behave this way! |
| Scripts | OnHeartbeat, OnUserDefined, ... -> CResRef script payloads. |
| State Flags | Flags (DWord) -> Bit 0 explicitly marks an Interior environment. |
| State Flags | RestrictMode (Byte) -> Hardcoded Event: Changing this to a non-zero value during gameplay forces CSWPartyTable::UnstealthParty. |
| Identity | CameraStyle (Int), DefaultEnvMap (ResRef), LoadScreenID (WORD) -> All three default unconditionally when absent: CameraStyle to 0, DefaultEnvMap to an empty resref, LoadScreenID to 0. |
Note
Internal Weather Truncation If
Flags(Bit 0) marks the area as an interior space, the engine zeros out all weather properties upon load, actively discarding any prior weather assignments.
Weather & Terrain Generation
| Field | Type | Engine Evaluation |
|---|---|---|
ChanceFog | INT | Stored persistently as an integer. Defaults to 0 if missing, unconditional – the same read shape as ChanceRain/ChanceSnow/ChanceLightning/WindPower (all default 0 too), just not narrowed to a byte. When the area’s interior flag is set, ChanceFog gets zeroed a second time in the post-process pass alongside the other four, so an interior area’s fog chance is forced to 0 twice over rather than once. |
ChanceRain, ChanceSnow, ChanceLightning, WindPower | INT | Warning: The engine explicitly truncates these INT properties to 8-bit bytes at runtime. Values over 255 silently wrap around. All four default to 0 if missing, unconditional. |
Grass_TexName | ResRef | If empty or invalid, the engine forces a hard fallback to "grass". |
AlphaTest | FLOAT | Defaults to 0.2 (older tools commonly assume 0.0). |
ModSpotCheck, ModListenCheck | INT | Perception-check modifiers. Both default to 0 if missing, unconditional. |
Grass_Density, Grass_QuadSize, Grass_Prob_LL/LR/UL/UR | FLOAT | Grass rendering density and per-corner spawn probabilities. All six default to 0.0 if missing, unconditional, identical read shape across the set. |
Area Lighting & Sun/Moon Tracking
KOTOR handles dynamic sunlight constraints separately between Sun and Moon.
| Property Groups | Type | Engine Evaluation |
|---|---|---|
Fog Ranges (MoonFogNear/Far, SunFogNear/Far) | FLOAT | Defaults to an immense distance of 10000.0. The engine aggressively clamps values to be ≥0.0. |
Tints (*AmbientColor, *DiffuseColor, *FogColor) | DWORD | Processed seamlessly as standard DWORD color masks. All nine (MoonAmbientColor, MoonDiffuseColor, MoonFogColor, SunAmbientColor, SunDiffuseColor, SunFogColor, DynAmbientColor, Grass_Ambient, Grass_Diffuse) default to 0 if missing, unconditional, identical read shape across the set. |
Environment Shadows (ShadowOpacity, *Shadows) | BYTE | Basic toggles and opacities orchestrating render limits. SunFogOn, SunShadows, MoonFogOn, MoonShadows default to 0/false if missing, unconditional. Unescapable, StealthXPEnabled, and StealthXPLoss are the one group in this table that genuinely carries over a constructed value rather than stamping a fresh literal – functionally identical to 0/false in practice, since LoadAreaHeader only ever runs once, immediately after construction, on a single call path. |
Note
DayNightCycledoesn’t inherit its own constructed default. The area constructor setsday_night_cycle = 1(cycle on) before any GFF read happens, but the read itself uses a hardcoded literal0as its fallback, not the constructed value – so an area missingDayNightCycleloads with the cycle forced off, silently overriding what the object was built with. This looks like it could be an engine oversight (the constructor’s own default is never actually reachable through this load path), but intent aside, the mechanism is unambiguous: absent means0, not1.IsNightandLightingSchemeshare the ordinary unconditional-0pattern with no such mismatch.
NoRest,TransPending,TransPendNextID, andTransPendCurrIDalso carry over a constructed value (all0/false), same practical outcome as an unconditional default given the single-call-path caveat above.
Note
Grass_Emissiveand the entireDirty*overlay set (12 fields) are confirmed dead in this K1 build, not merely unread. None of their field-name strings –Grass_Emissive,DirtyARGBOne/Two/Three,DirtySizeOne/Two/Three,DirtyFormulaOne/Two/DirtyFormulaThre,DirtyFuncOne/Two/Three– exist anywhere inswkotor.exe’s string table, verified against a binary-wide search that does find every neighboringGrass_*field. This is stronger than “the loader doesn’t consume it”: no code path in this build can even look these fields up. Treat them the same asDisableTransit/NoHangBack/PlayerOnly/PlayerVsPlayerbelow – toolset-only, invisible to K1’s engine.
Map Transitions & Saving states
| Feature Category | Engine Evaluation & Triggers |
|---|---|
| Minimap Logic | Geographic vectors (MapResX, spatial coordinate structs like WorldPt1X) are only loaded if an actual Minimap TGA/TPC asset matching the level name exists on disk! Two further gates sit behind that one: the Map sub-struct must itself be present in the GFF, and MapResX’s own resolved value must be nonzero. MapResX reads with an unconditional literal default of 0 – so an absent MapResX isn’t just “zero,” it’s a genuine gating sentinel, since that same 0 is then tested directly and, if it holds, skips reading NorthAxis/MapPt*/WorldPt* entirely and falls through to a fully disabled map initialization with MapZoom fixed at 1. |
| Parsing Type | If read, the engine parses MapPt along a dual-path logic checking if it is formally a FLOAT or INT type. An absent MapPt1X/MapPt1Y/MapPt2X/MapPt2Y resolves to 0 either way the type check goes – the INT branch reads a literal 0 directly, and the FLOAT branch’s own 0.0 default survives its floor-conversion step landing on 0 too. NorthAxis defaults to 0 if missing, unconditional. WorldPt1X/WorldPt1Y/WorldPt2X/WorldPt2Y are independent FLOAT reads, each defaulting to 0.0, unconditional, no gating between them. |
| Zoom Bias | Area maps evaluate MapZoom to a default scaling scalar of 1, not 0! |
| Stealth Save-States | The stealth framework leverages the .are struct to snapshot .StealthXPMax and .StealthXPCurrent directly as DWORDs when parsing the layout. |
Per-Room EnvAudio
Each entry in the Rooms list carries its own EnvAudio INT, read by LoadAreaHeader alongside that room’s AmbientScale, defaulting to 0 when absent. It’s a genuine per-room reverb/environment-audio zone selector, not inert data: CSWRoom::SetRoomEnvAudioProps matches each room by name and copies its EnvAudio/AmbientScale pair into the client-side audio setup that runs when an area loads. (The exact downstream reverb table or effect it selects wasn’t traced past that per-room handoff.)
Don’t confuse this with a same-named field on the GIT’s own area-level AreaProperties struct (see GIT’s AreaProperties.EnvAudio) – that one is a different struct entirely, and unlike this per-room field, it’s never read by anything.
Rooms and PartSounds: Remaining Field Defaults, and Two Confirmed-Dead Fields
No entry-level presence-chain abort exists in either list: the room loop and the nested PartSounds loop both process every index regardless of whether an individual GetListElement call succeeds, so a partially-specified entry is always kept, with each field defaulting independently.
RoomName defaults to an empty string if missing, unconditional – the same name SetRoomEnvAudioProps matches against a live room, so an empty RoomName simply never matches anything. AmbientScale defaults to 0.0, unconditional and independently gated from EnvAudio’s own default (the two are read back-to-back but neither gates the other). ForceRating and DisableWeather are confirmed dead the same decisive way as Grass_Emissive/Dirty* above: neither field-name string exists anywhere in swkotor.exe, confirmed by a binary-wide search. Toolset-only in this build.
PartSounds entries: Looping defaults to 0/false, unconditional. ModelPart defaults to an empty string, unconditional. OmenEvent is read as a CExoString, not the INT its name might suggest – confirmed by its sole cross-reference in the binary, landing on a ReadFieldCExoString call inside this exact loop – and defaults to an empty string, unconditional. Sound defaults to an empty resref, unconditional; the (possibly-default) result is appended to the room’s sound list regardless of whether the file actually supplied one.
Expansion_List Defaults
Expansion_Name defaults to an empty localized string if missing, unconditional. Expansion_ID defaults to 0, unconditional. The list itself is only processed at all if the Expansion_List field is present in the GFF – that’s a list-level gate, not a per-entry one – but once inside, no entry is ever dropped for a missing field, same as Rooms/PartSounds above.
The Minigame Struct
Read via CSWMiniGame::Load (0x006723d0). If a minigame context triggers, the .are reads the nested Type (DWORD mapping 1=Swoop, 2=Turret). It injects highly specialized float properties modifying basic terrain speeds:
| Field | Injection Default / Constraint |
|---|---|
LateralAccel | Defaults safely to 60.0. |
MovementPerSec | Scales to 6.0 (Swoops), 90.0 (Turrets), or 0.0 otherwise! |
Bump_Plane | Bounds are heavily clamped to 0..3. |
| Nested Arrays | The struct natively requires sub-struct Player arrays (Models, Camera, Axes) and Enemy/Obstacles lists to operate properly. |
The Player struct, each Enemies list entry, and each Obstacles list entry are three genuinely different shapes, not siblings sharing one flat layout – Player and Enemies share a common vehicle base (hitpoints, a nested weapon Gun_Banks list, Scripts, Sounds) with Player adding its own movement/track-boundary fields on top, while Obstacles are a much lighter leaf with only a Scripts struct. See the Swoop & Turret Minigame Deep Dive for the full field-by-field breakdown and absent-field defaults.
Implemented Linter Rules (Rakata-Lint)
Phase 1 (intra-resource, no context)
Implemented under rakata_lint::rules::are.
- ARE-001 (Context Discards): Warns when interior areas (
Flags & 1) carry non-zeroChanceRain,ChanceSnow,ChanceLightning, orWindPower; the engine discards weather for interiors. - ARE-002 (Weather Truncation): Warns when
ChanceRain,ChanceSnow,ChanceLightning, orWindPowerexceed 255; the engine truncates these to bytes at runtime. - ARE-003 (Fog Clamping): Warns when
MoonFogNear/FarorSunFogNear/Farare negative; the engine clamps fog distances to >= 0.0. - ARE-004 (Tag Lowercasing): Warns when
Tagcontains uppercase characters; the engine lowercases area tags on load. - ARE-005 (Toolset Fields): Informs when
DisableTransit,NoHangBack,PlayerOnly, orPlayerVsPlayerare set; never read by the K1 engine.
Phase 2 (resource existence, requires LintContext)
Implemented under rakata_lint::rules::are_range.
- ARE-006 (Resref Existence): Warns when any of
OnEnter,OnExit,OnHeartbeat, orOnUserDefined(.ncs) does not resolve, or when anyRooms[i].PartSounds[j].Sound(.wav) does not resolve in the configured resource sources.
Pending
- Grass Texture Fallback: Informs when
Grass_TexNameis empty; the engine treats this as the literal string"grass". - Texture / MiniGame Resref Existence:
DefaultEnvMap,Grass_TexName, and the nested MiniGame model / track / music graph – ResourceTypeCode mapping for engine-specific texture and model packs is still being audited.