Files
sacred_k/docs/widescreen.md
T
2026-10-14 04:21:37 +02:00

154 lines
7.3 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)
- 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
- Above ~2300 px render width (between 2304 and 2816) the player is not drawn
although monsters are (`cCreature::vf14` still called). Not hit at the
default size; some per-view limit of ~40 tile columns, not found yet.
- 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)?