- ra3.map: capture the object RoadType; ra3.models pairs the map's
Start/End road objects and drapes a flat textured ribbon along each
segment (tessellated to the relief).
- Ground decals (roads/sidewalks) get a per-vertex screen-space depth
offset (scene::vertex.bias) applied in the object shader and the
software rasteriser, so they no longer z-fight or clip into the
terrain at top-down angles.
- Tactical view aligned with retail (SAGE LookAtTranslator): middle-drag
orbits yaw/pitch, a middle click resets angle/pitch/zoom, arrow keys
pan, wheel zooms within the retail zoom range; WASD pan removed and
left-drag no longer rotates.
- ui_event gains middle/right/released buttons; Vulkan + SDL map them.
- docs: roads, depth bias and camera controls.
Read the buildings and props a map places from its compiled art (the
uncompressed worldbuilder stream + embedded DDS textures):
- ra3.models: BAB asset-stream parser (lazy slices), W3DMesh + D3DHierarchy
decode, DDS (DXT1/3/5 + uncompressed) decode, per-mesh bone remap and
bind-pose single-joint skinning, flattened to one world-space triangle soup.
- ra3.map: parse the ObjectsList chunk into (type, x, y, z, angle).
- ra3.terrain: render3d rasterises the scene over the raymarched terrain with
a z-buffer; gpu_terrain carries the scene for the GPU backends.
- ra3.vulkan: second pipeline + depth attachment, terrain.frag writes
gl_FragDepth, and a small depth bias keeps ground decals from z-fighting.
- objects.fx shaders (compiled to SPIR-V), embedded like scene/terrain.
Only opaque parts are drawn: FX-light billboards (DefaultW3D.fx / BasicW3D.fx)
and damage-fill shells (BuildingsGenericDamageFill.fx) are skipped, since the
latter paint the wrecked interior (e.g. orange CBBuilding_Wood) over the shell.
Ground-decal meshes with no diffuse role and the Road templates themselves are
still not drawn.
The engine now runs on a plain Web Worker (no SDL, no PROXY_TO_PTHREAD: in a pthread Emscripten proxies every filesystem syscall to the main browser thread, where synchronous XHR - and thus FS.createLazyFile - is forbidden). A page/worker pair transfers an OffscreenCanvas and forwards DOM input; assets load lazily from an embedded manifest, so entering a map fetches only that map and its tiles.
Presentation: ra3.webgpu (WebGPU via Emscripten's emdawnwebgpu port, WGSL shaders, the default on wasm) and ra3.wasmgl (WebGL2, GLSL ES). The legacy SDL ra3.webgl backend is removed.
- ra3.webgl: a Web peer of Vulkan/Direct3D (SDL3 canvas + GLES3/WebGL2), with
2D blit and the GPU terrain raymarch; GLSL ES shaders; null fallback off
Emscripten. backend::webgl + default_backend() (webgl under Emscripten).
- Emscripten toolchain + Dockerfile.wasm + scripts/build-wasm.sh; import std,
global -fexceptions, and Asyncify so the blocking frame loop yields to the
browser (display::sleep_frame calls emscripten_sleep).
- apps/web/shell.html + apps/web/serve.py (Range-capable dev server).
- Assets: browsers forbid synchronous on-demand reads on the main thread (and
FS.createLazyFile / the WasmFS fetch backend are worker-only; SDL3's Emscripten
backend is main-thread DOM only, so the engine cannot run in a worker). The
wasm build therefore preloads a compact per-map set via OPENRA3_WEB_ASSETS.
- New openra3 textures --map ID lists the loose terrain TGAs a map resolves to
(terrain::resolve_texture_files, shared with load_textures_from_dir).
- Fixes: webgl heightmap used GL_R16 = 0x8229 (that is R8) -> upload rejected,
terrain flattened to water; now 0x822A. Logger uses a stdout console sink on
the web (stderr maps to console.error); a GL error check logs bad uploads.
- CI: wasm image + build jobs.
- ra3.dx: D3D11 and D3D12 presentation backends (runtime HLSL via d3dcompiler);
2D image blit and the GPU heightfield terrain raymarch, with the corner
minimap/FPS overlays. Non-Windows builds link a null fallback.
- ra3.display: preferred backend plus ordered fallback (Vulkan/D3D11/D3D12/SDL);
menu gains a Renderer option and the CLI gains --dx11/--dx12/--sdl, which the
render command now honours too.
- vendor libenderlog (MIT): every run writes openra3.log next to the exe and
archives the previous run's log; records at warn and above carry a call stack
(native fallback, since libc++ has no <stacktrace>).
- Windows crash reporter writes openra3_crash.log (faulting module + backtrace);
D3D/DXGI diagnostics are routed through the logger.