Campaign loading and asset resolution
For measured menu/loading resource residency and reclamation priorities, see WC3 memory.
Loading-screen ownership
CL_BeginLoadingMap publishes the resolved destination and freezes a non-interactive loading plaque.
The client remains ca_connected while registering assets; the first usable server frame activates it once
CL_PrepRefresh has completed. SCR_DrawCursor hides both the native and authored cursors during loading.
SV_Map calls the mandatory game export PrepareMap before LoadMap. WC3's G_PrepareMap reads only W3I/WTS
through CM_ReadMapInfo, binds native UI/FrameDef/Glue/Loading.fdf, and serializes UI_WriteLoadingLayout
against that temporary metadata. G_MapString is shared with gameplay's G_LevelString so early chapter text
uses the same WTS rules. A custom W3I loading model takes precedence; otherwise the campaign background number
selects a model and sequence from UI/WorldEditData.txt's LoadingScreens section. Maps without either use the
decorated LoadingMeleeBackground skin entry.
The initial transport order is:
- Loading-phase configstrings: destination, asset scope, models, images, and fonts.
svc_loading_screenchunks: receiving the complete compressed layout decodes the frame tree, registers the preceding media, and repaints immediately.- Full configstring pages, then baseline pages.
- The final
svc_mirror "precache"permits world/model/image/sound registration; completion queuesbegin, then the first usable frame activates gameplay.
SV_BuildLoadingScreen compresses the complete authored layout into map-owned sv.loading. The payload is the
existing layer byte, delta-encoded UI frames (including text and type-specific buffers), and frame terminator.
It accepts only LAYER_LOADING, not a nested server-message stream. Compression and decode are bounded by
MAX_MSGLEN; there is no configstring-slot budget, zero padding, or text shortening. Failure to fit or compress
fails map startup with a diagnostic. Referenced media remain in the normal model/image/font configstring pools.
Protocol version 4 replaces the binary loading configstrings with this explicit message:
| Field | Wire type | Meaning |
|---|---|---|
| Opcode | byte | svc_loading_screen |
| Total | 32-bit integer | Complete compressed byte count, positive and at most MAX_MSGLEN |
| Offset | 32-bit integer | Chunk offset in that stream; zero starts/restarts assembly |
| Size | 32-bit integer | Positive chunk byte count, bounded by the remaining stream and packet |
| Data | Size bytes |
The corresponding compressed bytes |
SV_SendLoadingScreen accounts for the 13-byte header and preceding media when splitting at SV_SignonLimit.
UDP packets stay within BZ_SIGNON_SIZE (1,400 bytes); loopback uses the engine buffer limit. The client allocates
only the declared total, requires contiguous offsets and a consistent total, and releases the assembly buffer
on completion, error, or disconnect. Corrupt compressed data, truncated packets, and missing/out-of-order chunks
are rejected. This framing does not add retransmission or sequencing to the existing raw UDP transport.
Client and server must both use protocol version 4.
Three resource-pool endpoints are retained in sv.loading_end before gameplay adds resources. Media indices
remain stable as LoadMap extends the pools. SV_New_f replays the retained compressed layout and reconstructs
its media dependencies from the authoritative configstrings using those endpoints. The server releases the
compressed layout on map replacement, lobby entry, or shutdown.
The batch includes inherited Loading.fdf template media declarations, but excludes later world/gameplay media.
WC3's background/bar are models, so moving only images earlier is insufficient.
For a listen server, SV_Map establishes the connection and sends this loading batch before ge->LoadMap, then
calls CL_LoadingFrame. This limited loading pump invokes no command buffer, client tick, server tick, or gameplay
callback. On Ubuntu/Linux, it also calls SDL_PumpEvents so the window manager continues receiving events while the
synchronous map load owns the main thread; without this, Ubuntu can display “This window is not responding.” The pump
is throttled centrally and does not consume queued gameplay input. WC3 passes the required
game_import.LoadingFrame callback into CM_LoadMap for that invocation; the common W3 map parser runs its ordered
archive-section table and yields after each phase. G_LoadMap also yields between world/HUD/entity/script phases,
and large entity-spawn loops yield periodically. The callback is never retained in collision-model state. Dedicated
servers remain no-ops. This follows Quake 3's CG_LoadingString -> trap_UpdateScreen cooperative loading pattern;
do not run the full command/input event loop re-entrantly because queued session changes can invalidate the active
load stack. cl.precache_ready prevents the early layout/CS_WORLD from starting bulk registration until the full
configstring/baseline handshake reaches precache.
CS_ASSET_SCOPE and re.SetAssetScope establish map-import resolution before renderer world registration.
This matters for custom loading MDX models whose companion textures live inside the destination archive.
The normal registration pass retains those loaded handles; reconnect clears them at the existing client boundary.
Both background and progress bar retain the FDF's FT_SPRITE type. The background uses #!sequence for native
screen-space geometry. The zero-size bar uses #0 plus UI_STAT_LOADING_PROGRESS, a client-local normalized
binding, so SCR_LayoutDrawSprite supplies #0@ratio. No player-state field or network layout size changes.
FT_LOADING_BAR is the image-bar contract used by WoW; an image and a model may have the same numeric index.
Melee loading panels, minimap, and roster
Loading.fdf has distinct custom/campaign and melee panels. LoadingTitleText intentionally lives on the
right; LoadingMeleeMapName is anchored above MinimapImage. The old code always hid LoadingMeleePanel,
even for melee artwork. Choose the melee panel when W3I supplies neither a custom model nor a campaign background.
The border around the minimap is part of the background MDX, so an empty border does not prove a minimap frame drew.
No authored title/minimap geometry needs overriding.
G_PrepareMap applies the same gi.ApplyLobbySettings used by G_LoadMap to its temporary W3I metadata before
serializing loading. Native LoadingPlayerSlot clones show participating human/computer names and selected races;
empty/closed slots and neutral players are excluded. Names resolve WTS for direct launches and use lobby names
for configured games. Columns repeat the FDF container width and row height. Team headings, player-name colors,
and synchronized per-player ready highlights are still separate presentation work; no ready state is fabricated.
FT_MINIMAP plus UIFLAG_MINIMAP_PREVIEW uses frame text as the destination archive reference. The game renderer
loads war3mapMap.blp (or an authored TGA) and war3map.mmp directly, before terrain registration. This static path
never samples fog, draws the camera outline or live pings, or registers a clickable gameplay minimap. W3I's
hide_minimap_in_preview_screens flag is respected. MMP v0 consists of two 32-bit header words and 16-byte records
(type, X, Y, BGRA). Coordinates refer to the full 256-pixel thumbnail, including letterboxing. Types 0/1/2 use the
stock gold, neutral-building, and Circle of Power textures. Start-marker colors are the authored MMP colors,
not lobby-remapped colors or final randomized spawn positions. Missing/corrupt assets are logged. This preview-only
MMP path is separate from the live automatic contacts documented in minimap markers. Live Hero,
Gold Mine, racial-mine, and neutral-building marker paths come from war3skins.txt Game Interface fields with
war3mapSkin.txt CustomSkin precedence after map asset scope is active; missing live art is diagnosed and shown with
the renderer placeholder rather than silently demoted to an ordinary contact.
The first implementation stored the preview-bit result in BOOL, an unsigned byte. A bounded Bandit Ridge trace
showed flags 0x802a arriving intact but the narrowed preview value becoming zero, drawing the unloaded gameplay
minimap's white texture. Use C bool for this high-bit membership result. The network test covers delta decoding
through the actual minimap draw dispatch, as server-only frame inspection cannot catch this narrowing.
A twelve-player Emerald Gardens layout measured 3,610 bytes before compression and 789 bytes compressed, exceeding
the original 512-byte limit even with text shortening. Protocol version 3 expanded that allocation to eight binary
slots. Protocol version 4 removes the slots and their text budget entirely: svc_loading_screen carries complete
native rows, lobby names, and minimap archive references within the engine message limit. Chunking bounds each
network packet. The core, loading-FDF, and server signon tests cover these contracts.
The local data/WarsmashModEngine checkout's MenuUI.java binds LoadingMeleePanel but keeps it hidden;
internalStartMap populates the custom panel. It is not a complete retail melee-loading implementation.
For an early framebuffer capture, queue screenshot 2 before map in an exec script, then run bounded:
printf 'screenshot 2\nmap "Maps/FrozenThrone/(2)BanditRidge.w3x"\n' > /tmp/loading-check.cfg
build/bin/openwarcraft3 -data 'data/Warcraft III' -tft +vid_hidden 1 +com_frame_limit 30 +exec /tmp/loading-check.cfg
Unlike startup +map processing, the script orders the screenshot request before synchronous map preparation.
Screenshots are written under screenshots/. Bandit Ridge, a configured Twisted Meadows lobby, and twelve-player Emerald Gardens were visually
checked with visible, unfogged terrain and authored markers.
September 7 initial-layout regression
Commit aa5f89f73 moved the loading screen from the menu to the initial server layout but lost campaign-row
resolution and changed native sprites into portraits. A bounded Human02 trace confirmed background number 3
was registered as nonexistent LoadingMeleeBackground.mdx, while the bar's portrait viewport was zero-sized.
Once images registered, the same model index (1) selected an unrelated image. Restoring native sprite types,
the ROC/TFT row parser, and an explicit progress binding fixes all three without changing FDF geometry.
September 9 direct-launch timing investigation
Before the early batch, a bounded macOS Human02 trace showed the initial loading draw had no layout. The layout
arrived at 4.82 s, but background/bar model handles were still NULL at progress 0.10 and 0.40. World registration
preceded the entire model pass, so the first artwork was presented at 7.81 s and gameplay began at 8.63 s: only
about 0.8 s of loading artwork. Menu FDF initialization took about 61 ms. Earlier swaps were real but lacked content.
History identifies aa5f89f73 (wc3: move loading UI into initial layout) as the late-publication boundary.
After the fix, a local Human02 run presented loading art at 4.61 s, immediately before entering bulk server loading at 4.61 s; that work finished at 6.05 s. Its initial batch was 4405 bytes. WoW Azeroth follows the same early batch path; its separate artwork defects and visual verification are covered in WoW data loading. These are individual hidden-window observations, not timing guarantees: renderer/game initialization and loading-presentation metadata/assets still take startup time.
build/bin/openwarcraft3 -data 'data/Warcraft III' +screenshot 2 +map 'Maps/Campaign/Human02.w3m' +vid_hidden 1 +com_frame_limit 100
build/bin/openwow -data data/world-of-warcraft +screenshot 2 +map 0 +vid_hidden 1 +com_frame_limit 100
run-map does not forward ARGS, so use the binary for bounded diagnostics. To measure, temporarily probe
SV_BuildLoadingScreen, CL_PrepLoading after its swap, ge->LoadMap entry/return, and SCR_EndLoadingPlaque
with SDL_GetTicks() / fprintf(stderr, ...); remove the probes afterward. Hidden-window rendering still needs
the Mac display server; a sandbox without displays fails before map loading and gives no useful timing.
Quake III's analogous principle is client registration of presentation before expensive loading:
CG_DrawInformation obtains the
map name from server info and registers levelshots/<map>.tga, while CG_LoadingString repaints through
trap_UpdateScreen. Its download path
handles referenced PK3 downloads before cgame initialization; it does not stream a rendered loading screen.
Our server-authored layout uses the existing media configstrings instead of Quake III's levelshot naming convention.
June 28 regression
Commit b6349c392 removed cl.loading_map/GetLoadingMap but left CL_BeginLoadingMap ignoring its
mapName argument. UI lookup could only see a startup +map cvar, explaining why direct map launches
worked while campaign buttons produced a black background plus LOADING. Runtime diagnostics for Human01
showed a valid requested destination and an empty UI map cvar. In addition, the UI looked up its own
cached path, preventing invalidation on subsequent maps. Both paths now use the published destination.
TFT has an additional independent schema difference: ROC LoadingScreens rows are
label,sequence,model, while TFT rows are expansion-category,label,sequence,model, including ROC campaigns
under TFT archives. Fixed ROC indices interpreted HumanX01's sequence 6 as a filename. UI_ParseLoadingRow
consumes the optional numeric category and validates the sequence/model fields; malformed rows log their key.
In that menu-owned implementation, the loading-state draw check had to precede standalone-screen dispatch because
menu_ingame is queued asynchronously. The initial-layout path now dispatches loading from SCR_DrawScreenField.
Historical menu FDF registry isolation
libmenu and libgame intentionally have separate stb_fdf frame registries. The parser implementation functions are hidden per shared library, and the frames[] backing store must be hidden as well. On ELF/Linux, exporting frames allows normal symbol interposition to alias the two registries even though each library defines its own copy. G_LoadMap -> UI_ResetHud -> UI_ClearTemplates then clears/reuses the menu's live Loading/LoadingBar slots during SV_Map; cached non-NULL loading-frame pointers survive but no longer describe the loading sprites, so later progress updates reach the old M_DrawLoadingScreen without reaching the MDX renderer.
A characteristic failure is that the initial loading sprite renders before SV_Map, while later progress values change without reaching the MDX sprite renderer. Verify registry isolation before changing progress math or swap/present code.
Level-transition asset ownership
A campaign ChangeLevel is a map replacement, not a mutation of the current world. The client clears its
map-owned renderer handles before the next registration sequence, while G_LoadMap clears the server-side model
metadata cache because CS_MODELS indices restart for each SV_Map. The WC3 map loader releases the previous
map's W3I/WTS/object-modification allocations, placements, terrain arrays, and pathing storage before reading the
destination archive. These boundaries prevent a model index, trigger string, placement, or pathing allocation from
retaining the previous level by accident.
The WC3 renderer likewise replaces its map state at _W3M_RegisterMap: old terrain segment/layer VAOs, ground
layer lists, cliff model choices, fog render targets, minimap handle, terrain shadow, and source W3E arrays are
released before the destination world is built. Ground and cliff lookup caches are map-scoped and reset at the
same boundary. Do not append a new campaign map to the previous terrain lists.
Map-imported model and texture overrides
The MPQ reader supports nested archive paths: a lookup such as
Maps\Campaign\Human03.w3m\Textures\Foo.blp opens Human03.w3m and then resolves the inner path. WC3 installs
the currently registered map as the renderer's generic map-asset scope, so ordinary model and texture references
resolve with this order:
<current .w3m/.w3x>\<logical Warcraft asset path>;<tileset>.mpq\<logical path>when the map's nested tileset archive contains it (cliffs, uber splats, water frames; see terrain-tiles-and-water.md);- the ordinary base-data path.
The resolved scoped path is also the renderer cache key. This is required for consecutive maps that import
different bytes under the same logical name; a Textures\Foo.blp cached for Human02 must not satisfy Human03's
own Textures\Foo.blp. Missing scoped models/textures fall back to Blizzard data without turning the scoped miss
into a missing-resource placeholder. Texture fallbacks alias the scoped key to the resolved base texture so later
lookups do not repeat the nested-archive miss. Clearing client state clears the generic renderer asset scope so a
map import cannot continue overriding menu assets after disconnect.
Standalone renderer regression coverage exercises both a present scoped model/cache hit and a missing scoped model that falls back to the base path, including scope clearing at a registration boundary.
This is intentionally narrower than a full Warsmash-style data-source stack. The current transition still does
not rebuild all WC3 SLK/TXT data from scratch for every unrelated reason, merge all war3map.w3t/.w3b/.w3d/.w3q object modifications,
load assets through JASS Preload/Preloader or preload-file generation (the PreloadRefresh and PreloadEndEx
batch markers are no-ops), or expose staged byte/task loading progress. war3map.w3u now applies the
registered UnitBalance/UnitProfile/UnitUI subset (including balance/stock values, Required Animation Names, and custom model
paths), but the remaining Data/Weapons tables and full AbilityMetaData-driven war3map.w3a field coverage remain separate data-layer work;
do not infer those capabilities from the renderer's map-import lookup. DotA 6.83d ships heroes, items, and
war3mapMisc.txt inside the map archive (Units\CampaignUnitFunc.txt, war3map.w3a); after #432
CM_LoadMapFormat mounts that archive via FS_SetPriorityArchive so G_ReadGameDataFile sees those members
before Custom_V*/base, and CM_ReadAbilities / G_SetMapAbilityOverrides apply DataA–I plus common leveled
fields. See DotA Custom-Map Playability and WC3 Data Model.
Loading progress contract
Loading progress is client-owned and intentionally coarse. CL_BeginLoadingMap resets cl.loading_progress to
zero. CL_PrepRefresh advances it monotonically at existing registration boundaries and
SCR_UpdateLoadingPlaque explicitly repaints the otherwise frozen Quake-style loading plaque after each advance.
The initial loading batch carries the Loading.fdf tree as svc_layout; its FT_SPRITE bar binds
UI_STAT_LOADING_PROGRESS to the LoadingProgressBar MDX sequence using #0@ratio. No player-state/network field is involved.
Current phase values are:
| Progress | Completed boundary |
|---|---|
| 0.05 | local listen-server SV_Map() returned successfully (local games only) |
| 0.10 | CS_WORLD is available and client refresh preparation has started |
| 0.40 | collision/world map and renderer map registration are ready |
| 0.60 | model configstrings are registered |
| 0.75 | image configstrings are registered |
| 0.87 | sound configstrings are registered |
| 0.94 | font configstrings are registered |
| 0.98 | client begin has been queued |
| 1.00 | sound registration is closed and cl.refresh_prepped is true |
These are phase milestones, not byte percentages or time estimates. In particular, local SV_Map() /
ge->LoadMap() remains synchronous and publishes only its completed boundary, not progress from inside its
server/game loading work, so the already-visible bar remains at its initial position during a long map load. Do not manufacture sub-percentages for that work;
add progress only when an owned lifecycle can report a real boundary. The plaque stays visible at 100% until the
first usable server frame promotes the client to ca_active and SCR_EndLoadingPlaque runs.
Asset names are data
| Source | Meaning / resolution |
|---|---|
Units/DestructableData.slk, texFile |
_ (or empty/absent) means no replacement image. Preserve an authored extension; do not append .blp in the spawn code. Human01 LT05 uses ReplaceableTextures\Cliff\Cliff0.tga; crates/gates use _. |
Texture references ending in .tga |
The exact file wins. If absent, R_ReadTextureFile tries the same stem with .blp, then normal renderer diagnostics/placeholders apply. This handles converted source names without overriding real custom TGA files. |
ReplaceableTextures\CameraMasks\White_mask.tga |
Human01's cinematic script requests this; the local ROC archive contains White_mask.blp (1422 bytes), not TGA. The cliff BLP is 90592 bytes. |
Native StandardTemplates.fdf |
Some unused templates reference art absent from ROC archives (HeavyBorder*, LightBorder*, ButtonBackGround, ButtonCorners, GlueScreen-PlayerButton-BorderRight, GlueScreen-ROC-EditionButton-*). Parsing records texture keys/indices; UI_GetTexture loads only on consumption. Do not replace missing art with invented paths or suppress renderer warnings for consumed resources. |
UI_GetTexture caches both real and placeholder renderer handles. Decorated names are re-evaluated on theme
changes; an unchanged theme does not reload. Parsing or inheriting a template alone must not fetch its art.
Cliff-transition classification
Terrain vertices are ordered NE, NW, SE, SW by GetTileVertices; model configuration letters use
SW, NW, NE, SE. A transition requires exactly two adjacent ramp corners one cliff level apart.
Two same-height flagged corners belong to an adjoining ordinary cliff, not a sloped transition.
Human01 cell (64,31) yielded model-order levels [1,0,1,1] and ramp flags [1,0,0,1].
The former GetTileRamps(tile) > 1 test built nonexistent CliffTransHABH0.mdx and omitted its geometry.
Correct classification selects the ordinary CliffsBABB0.mdx. The native transition directory contains
L/H and H/X edge pairs, not an HH or LX edge. Do not turn file-not-found into a guessed terrain fallback.
The Warsmash terrain loader
is a useful secondary reference for separating transition edges from regular cliff cells; local archive
contents and logged vertex metadata establish this case.
Building footprints on terrain transitions
UnitUI.uberSplat (groundTexture) becomes edict.s.splat in SP_SpawnUnit, then the client submits it as an
entity splat. R_RenderUberSplat uses the lit world shader so the footprint receives terrain lighting and fog.
R_GenerateSplatTiles emits only ground-owned cells; R_TileHasGround excludes cells occupied by cliff transition
meshes. The splat rectangle may still end inside an adjacent ground cell.
Before the partial-tile clip, R_MakeSplatTile emitted that whole cell with UVs outside [0,1]. The texture uses
GL_CLAMP_TO_EDGE, and the lit world shader has no splat UV crop, so an opaque border texel could extend to the
cell edge as a straight strip beside a building. The regular splat and shadow shaders crop UVs, which made the
building footprint look different. R_MakeSplatTile now clips each of the cell's two terrain triangles to the
rectangle and interpolates height along the original triangle edges. Whole cells retain their six-vertex path.
Do not replace the building's shader with the unlit splat shader to solve this edge; that would lose its terrain
lighting and fog behavior.
Clipped edges introduce CPU-interpolated vertices instead of reusing the terrain's exact endpoints. Although they
lie on the same mathematical plane, their rasterized depth can differ by enough precision to flicker under
GL_LEQUAL. The WC3 splat pass therefore applies polygon offset (-1, -1) while drawing terrain decals and restores
the state afterward. This covers both lit building footprints and unlit selection rings without lifting either mesh
in world space.
renderer_terrain.splat_rect_stops_at_partial_tile_edge reproduces the out-of-bounds vertices and UVs;
renderer_terrain.clipped_splat_follows_both_terrain_triangles checks the height on a non-planar cell; and
renderer_terrain.splat_draw_biases_coplanar_terrain_geometry locks the depth-bias state contract. Run them through
make test-renderer-model test-renderer-shadows. A framebuffer comparison against a real map remains a visual check
beyond these headless geometry assertions.
Diagnostics and verification
build/bin/mpqtool -mpq 'data/Warcraft III/War3.mpq' cat Units/DestructableData.slk
build/bin/mpqtool -mpq 'data/Warcraft III/War3.mpq' ls ReplaceableTextures/CameraMasks
build/bin/mpqtool -mpq 'data/Warcraft III/War3.mpq' ls Doodads/Terrain/CliffTrans
build/bin/mpqtool -mpq 'data/Warcraft III/War3.mpq' cat UI/FrameDef/Glue/StandardTemplates.fdf
build/bin/openwarcraft3 -data 'data/Warcraft III' +map 'Maps/Campaign/Human01.w3m' +com_frame_limit 100
build/bin/openwarcraft3 -data 'data/Warcraft III' -tft +map 'Maps/FrozenThrone/Campaign/HumanX01.w3x' +com_frame_limit 100
make test-renderer-model test-ui
make test
For the menu regression, launch without +map, then select Single Player → Campaign → Human → the desired mission. The console
registers menu_single_player_campaign, but menu_single_player_campaign_human is a UI-handler command,
not a console command. Campaign selection now enters MissionSelectFrame; the selected mission's
menu_single_player_mission_select N handler is what issues the map command. A diagnostic replay can temporarily
invoke those handlers after showing the campaign screen; remove the hook afterward. Queue screenshot 2 before
the mission handler to capture the frozen loading plaque.
Use +com_frame_limit 100 for bounded runs; engine screenshots appear under screenshots/.
Regression tests cover texture extension lookup and exact-file precedence, SLK replacement sentinels,
ROC/TFT loading-row schemas, rotated/equal-height/diagonal cliff edges, unused FDF art, lazy texture cache hits/misses and theme changes,
and native loading-sprite serialization, client progress binding, and separate model/image namespaces.
Campaign artwork needs ROC/TFT runtime verification against real archives. Use explicit -roc and -tft
when a saved fs_expansion setting might override the default.
See also UI authoring, scene workflow, and filesystem loading.
The wc3_loading.initial_layout_resolves_campaign_custom_and_melee_art test uses tests.mpq's native
Loading.fdf, skin keys and WorldEditData.txt rows. It calls the production loader and writer, then inspects
emitted sprites, model configstrings, title/subtitle/body, sequence and progress binding. Cases cover ROC and
TFT row schemas, custom-model precedence, and default skin decoration. Restoring the pre-fix
hud_loading.c makes this test fail. The client drawing tests additionally check progress 0/0.5/1 and collisions
between model and image indices.
The shared net.loading_batch_registers_media_before_full_precache test verifies that a partial chunk cannot
publish the screen, initial model/image handles become available after complete assembly, later world media
remains deferred, and only the final precache reply opens the registration gate. Other client tests reject
corrupt/truncated/oversized payloads, missing chunks, and changed totals.
server_net.loading_batch_precedes_world_and_retains_resource_indices exercises a later connection against the
retained layout and resource endpoints. make test-server-net also covers complete text above the former 2 KiB
limit, binary preservation over real UDP within the signon budget, and maximum-size loopback chunking.
The WoW game test verifies that PrepareMap resolves and writes loading art before clearing/spawning the world.
The version-4 change passed the 156-test core suite, 32-test server network suite, and 59-test WoW game suite,
plus WC3/WoW/SC2 builds. A bounded Human02 launch with -roc +vid_hidden 1 +com_frame_limit 100 reached
CL_SendBegin and G_ClientBegin; this confirms registration and gameplay entry, not visual asset completeness.
The September 9 configstring refactor was checked with make test (2,855 tests), builds of all three game
binaries, bounded Human01/Human02 runs, Human02 → Human02 → Human01 reloads, and WoW Azeroth map entry. The Human02
source layout measured 560 bytes (including its original opcode), and compressed to 303 bytes before removing that
opcode. Both chapters retained their complete text and native artwork in early engine screenshots. Neither needed
text shortening. Those measurements describe the historical binary-slot transport; the current transport uses
svc_loading_screen as specified above.
A bounded console-script run also exercised Human02 → Human02 → Human01, with 100 wait commands between map
commands. All three reached G_ClientBegin, and early screenshots showed the correct chapter/sequence at zero
progress, including the same-map reload. Use actual map commands in an exec config for this check: repeated
startup +map arguments are cvar assignments, so the last value wins instead of scheduling multiple transitions.
LAN loading uses bounded startup pages and an engine-owned client map reader; see LAN lobby and startup for the timeout, UDP-size, and remote-only import-crash fixes.
Special terrain doodad cave placement and cliff replacement textures
The 16-byte special-doodad records after the ordinary war3map.doo placements
are terrain-cell anchored, not conventional world-space doodad transforms. The
last two words name the minimum tile coordinates. The second word is reserved
metadata and must not be added to the sampled ground height. Warcraft's
Doodads.slk pathTex defines a footprint in 4-pixel-per-cell coordinates.
The model is centred at (map.center + tile * 128 + pathTexPixelSize * 16)
per XY axis, with fixedRot and defScale supplied by the SLK row. The
original footprint remains authored data, not a hardcoded cave offset.
Models with MDX replacement ID 11 use the nearby W3E-selected CliffTypes.slk
texture (texDir/texFile) from the current map tileset. Do not make slot 11
white or replace it globally with a fixed Cliff0: the correct cliff tile varies
by map location and tileset. Explicit unit/destructable skins retain priority.
Special terrain doodads can replace generated cliff geometry under the
rectangular pathTex footprint. The WC3 renderer reads the special placements
in the war3map.doo tail, resolves Doodads.slk pathTex (including W3D
custom doodad ID inheritance and map overrides), and marks covered 128-unit
cells before baking terrain. The cliff baker skips those cells, including on
terrain rebuilds. The ground baker and splat placement do not use this mask:
normal W3E ground tiles and overlays remain visible where R_TileHasGround
permits them. This is intended to reduce the rectangular ground void seen behind the
OrcX01 Goblin Cave when both ground and cliffs were suppressed. The mask is visual
only; gameplay pathing/collision and terrain height queries stay unchanged.
Missing or malformed pathing textures do not remove geometry. The mask is
cleared on map replacement.
Warsmash's War3MapViewer suppresses both ground and generated cliff cells
for special doodads, but OpenRealm renders the special cave as an ordinary
height-adjusted MDX entity. For the observed OrcX01 result, retaining authored
ground while suppressing generated cliffs is a narrow compatibility adjustment,
not yet a verified retail-equivalent terrain replacement rule. This does
not fabricate ground on W3E cliff-transition tiles where R_TileHasGround is
false, so any remaining open gap must be investigated before widening the
cutout or adding synthetic geometry. The MDX cave model's position, scale,
rotation and replaceable cliff-texture selection are unchanged.
Selective cliff joins at special cave mouths
The initial special-doodad cutout removed every generated cliff tile within
its pathTex rectangle. In Rexxar's OrcX01 Goblin Cave this left visible,
straight model boundaries along the roof and sides, even once ground rendering
and the cave's terrain-sampled Z were corrected. Cliff cutouts now keep the
north/rear footprint row and both side columns above the front row, retaining
surrounding authored cliff pieces to cover the model's upper and side seams.
The front row still suppresses cliffs all the way across the mouth, preserving
the opening and shimmering barrier. Tiny or clipped footprints too narrow for
an inset retain full suppression. This is a visual-only, provisional join
policy; the original map's special doodad/MDX placement should be compared
with retail before treating the north/rear assumption as universal. No pathing,
height, ground, model scale/rotation, or textures are changed.
Special terrain doodad elevation (W3E terrain height)
Special war3map.doo records provide indexed XY cells, not an absolute model
Z. OpenRealm currently renders special doodads through the ordinary MDX entity
path, which does not deform their vertices using the W3E heightmap.
Previously the spawn helper forced every special model origin to Z=0, even
where surrounding terrain/cliffs were raised. That could bury the cave model
behind the terrain, leaving only stray geometry visible after cutouts.
After pathing-footprint XY centering, the WC3 game module now samples the
underlying W3E terrain at that XY (using CM_GetHeightAtPoint, including the
map's cliff-level baseline) and sets the special doodad only to that Z.
Sampling is clamped one-half tile inside the W3E vertex bounds for edge/
malformed placements; the actual drawn XY is never clamped. No hard-coded
cliff offset is added, and normal doodads, Forcewall entities, cutout masks,
textures and collision remain unchanged.
The exact retail mesh/heightmap deformation for these special models has not been visually verified from OrcX01 assets. Compare its Goblin Cave against retail: if the cavern silhouette remains incomplete, investigate the model origin/vertex geometry and terrain-cutout correspondence before adding further offsets.
Web-only early map loading placeholder
Before the authored map-specific loading screen arrives, a neutral browser overlay remains visible. Its handoff is triggered from a submitted loading layout frame, not merely from asset registration. It does not change the original Warcraft III loading-screen progress, artwork or presentation.