Files
sacred_k/docs/widescreen.md
T
khallaandClaude Opus 5.5 d39f12ab4b widescreen: draw creatures across the whole view; HUD: copy the cursor per thread
- Creatures were culled beyond 1024+200 x 768+200 screen pixels (cmp 4C8h /
  3C8h in both object passes): invisible in the right part of a wide view,
  and the player too once the centre passed 1224. The bounds follow the
  render size now.
- Centre immediates written as negatives (-200h / -180h), missed before:
  the row renderer's static object origin (0x62babb) and aiming at the
  cursor (0x4fb620).
- HUD: moved windows no longer shift the shared mouse cursor object; the
  cursor accessor (0x6550f0) hands them a per-thread shifted copy and writes
  back what they changed. World input on other threads read the shifted
  position now and then, which made hold-to-move jerky.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:16:27 +02:00

7.6 KiB

Widescreen: how the game sizes its view

Goal: render the world 1:1 at the monitor's native resolution, showing more of the world instead of scaling the 1024x768 image. Render and activity (animation/AI) distance must grow with it. UI keeps its 1024x768 layout.

Frame (render thread)

cEngine::renderThread loop, 0x0060e3f0, per frame:

  1. clear (D3D device +0x28)
  2. updates: 0x401660, 0x640b50, 0x634210, weather 0x417e50/0x41a4d0
  3. world: view->vf14(device) where view = engine[+8 + idx*4] (cWorldView0)
  4. UI: 0x756820()->0x7587b0(device), debug overlays 0x60f3e0/0x60f500
  5. FPS overlay 0x60aa90, DxDriver7::Flip

Skipped entirely while g_appFlags & 0x20000 (app inactive).

World view (cWorldView0, vtable 0x008900f8)

Offset Meaning
+0x14/+0x18 camera position (world pixels)
+0x1c/+0x20 requested camera position (scroll target)
+0xb60 zoom (float, clamped to 0.5 .. 2.0)
+0x96ab0 view width = zoom * 1024.0
+0x96ab4 view height = zoom * 768.0
+0x96ac0 extra rows (4)
+0x96ac8 columns = width/96 + 12
+0x96acc rows = height/48 + 24

Functions:

Address Name Notes
0x00624ae0 cWorldView0::SetupView computes the size fields above from zoom and the float constants 1024.0 (0x0088e6f4) and 768.0 (0x00890040); tile corner offsets ±48.2/zoom, ±24.2/zoom
0x006322b0 cWorldView0::Render (vf14) iso tile grid: tiles 96x48, two staggered half-rows per row (0x62b000), first tile = camera - (width/192, height/96)
0x0062a730 cWorldView0::UpdateCamera (vf0c) scroll blocking against sector edges uses width/2, height/2
0x00628210 cWorldView0::SetZoom clamps zoom, sets the ortho projection for 3D models: x ±267zoom, y ±200zoom, z -600..2500, then calls SetupView
0x00623c40 world -> screen projection x * 1024.0 / (2/P00), y * 768.0 / (2/P11)
0x006426a0 build ortho matrix (l, r, b, t, n, f)

All tile/screen positions are pre-transformed (FVF 0x1c4: XYZRHW, diffuse, specular, tex1); 3D models use the ortho projection.

Users of the 1024.0 / 768.0 float constants

World view (should follow the new size): 0x61e5c2, 0x61e5ec, 0x623c82, 0x623c9c, 0x624af2, 0x624b03, 0x628f65, 0x628f8f, 0x629294, 0x6292bc, 0x62b43a, 0x62dc32, 0x62ea2b, 0x62ea5e, 0x62f30d, 0x62f345, 0x630656, 0x63068a, 0x630f3f, 0x630f77 (mostly on-screen clip tests).

Elsewhere (unknown / UI, leave alone for now): 0x40694a, 0x4ec036, 0x593c25, 0x593c3a, 0x6ccb87, 0x6ce156, 0x758a34, 0x75f79a, 0x75f7b8.

Ortho projection extents (±267 / ±200 pushed as floats)

0x60c1f7, 0x60da54, 0x625039, 0x6283b3, 0x62858d, 0x628d3b.

Other frame producers (laid out for 1024x768: "classic" frames)

Address What Thread
0x00756940 cUI_Manager thread loop: main menu (clear, update, BeginScene 0x646f50, UI render 0x7587b0 incl. the menu's 3D scene, EndScene 0x646fd0, Flip) own UI thread
0x006469d0 loading screen: GDI BitBlt of bitmap + progress bar + text into the back buffer (fixed 1024x768) render thread
0x006a0c60 video/intro playback (unexamined)

The widescreen mod gives these frames a 1024x768 D3D viewport (set at BeginScene when the scene depth +0x1d8 is 0) and the borderless mod presents only that area, scaled to the monitor.

Direct3D 7 render target limit

Windows' D3D7 runtime refuses IDirect3D7::CreateDevice on any render target wider or taller than 2048 (DDERR_INVALIDOBJECT, 0x88760082), measured with a standalone test: 2048x1440 works, 2049x1440 / 2560x1080 / 3440x1440 fail. The default render size (768 high, e.g. 1835x768) stays below it; larger sizes are clamped to 2048. (A D3D7 -> D3D9 wrapper, dxwrapper, lifted the limit but was dropped again in commit history once the render size no longer needed it; it also required fixing the device info GUID pointer at +4 of SetDisplayMode's first argument, which its enumeration left invalid.)

Renderer

DxDriver7::SetDisplayMode (0x00645390) creates back buffer, z-buffer and D3D viewport from DxDisplayMode::width/height (hardcoded 1024x768 by InitGame, 0x00815620, which also calls ChangeDisplaySettings(1024x768) in fullscreen).

Fixed 1024x768 elsewhere (later)

  • 0x00613710 engine capture (save-game thumbnail), 0x006483f0 TGA writer
  • 0x0060e3f0 GDI BitBlt of a 1024x768 bitmap (startup/movie path)
  • 0x00627260 name/HP labels while Alt is held (no constants, uses view)

Render size

Default: 768 world pixels high (the original vertical view), width from the monitor aspect (3440x1440 -> 1835x768), scaled to the monitor by the borderless mod. Everything keeps the on-screen size players know; the view only grows horizontally. [widescreen] Height=0 renders natively 1:1 (everything about half size on a 1440p monitor).

Things that had to follow the render size

  • view size / centre / clip floats and their integer immediates (world view)
  • tile draw list and ground quad list (fixed 1750 / 1749 entries) moved into an enlarged view object
  • 3D ortho extents: world (+-267 x +-200, zoomed and unzoomed) and cGranny (+-512 x +-384, incl. cUI_NetGranny for HUD items)
  • creature clip test in both object passes (0x62f988, 0x6315cb): creatures are drawn only within -200 .. 1024+200 x -200 .. 768+200 screen pixels (cmp 4C8h / cmp 3C8h); unpatched, creatures in the right part of a wide view were invisible, and above ~2450 px width (centre > 1224) even the player
  • centre immediates written as negatives (add reg, 0FFFFFE00h, lea [..-200h]), missed by a search for 200h: the row renderer's static object origin (0x62babb) and aiming at the cursor (0x4fb6ef)
  • the inverse projection 0x623a20 (screen -> view) uses 1/1024 and 1/768: game logic (camera follow 0x44d9f0) converts world -> screen -> view with the device's current projection; unpatched it sent the camera off-world

Screen-space overlays

  • Alt object labels (0x627260) and floating texts / damage numbers (32 slots, drawn from the render loop at 0x60e940 via 0x41e460) are packed without overlap into a screen-sized area (0x662300, push 300h / 400h): widened to the render size (kLabelAreaPushes).
  • More world -> screen conversions with integer centre immediates (x - camera + 200h): the light/effect list at +0xce14 (0x41b1xx) and the floating texts (0x41e560, inverse 0x41eaf0). Unpatched, lights were offset from the player.
  • Left alone: push 300h / 400h at 0x4cc7d3, 0x5d7bb7 (1024x768 textures), 0x60e869 (GDI BitBlt), 0x613859.. (screenshot pixel conversion), and 200h/180h pairs at 0x52bab7, 0x52c6c0, 0x6c...-0x72... (UI windows, 1024 layout).

Activity (simulation) distance

The world is split into sectors of 64x64 tiles (cSector, manager **0xad3bc0, sector pointers at +0x240, flags +0x30: 0x10000 loaded, 0x800 active, tile origin +0x44 / +0x48). The 3x3 sectors around the player's sector are active (Sector[%4d] Active (3x3 sectors), test 0x638ff0); about 80 are loaded. Measured in game: 9 active sectors, origins 64 tiles apart.

With 96x48 px tiles, a sector spans 6144 px horizontally and 3072 px vertically, so the active block reaches at least that far beyond the player's sector in every direction. The widened view needs at most +-1835 x +-768 px (1835x768 at zoom 2), so everything visible is simulated; nothing to change.

Open questions

  • Fixed-size per-view buffers (e.g. +0x80e40, 5 vectors at +0x96ad0 with capacity 0x200) - do they overflow at 3440x1440 (47 columns x 54 rows)?