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

159 lines
7.6 KiB
Markdown

# 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 ±267*zoom, y ±200*zoom, 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)?