Files
sacred_k/docs/widescreen.md
T
khallaandClaude Opus 5.5 df31c9c105 Widescreen: keep original size, extend view horizontally; fix player/camera
- Default render size is 768 high and as wide as the monitor aspect
  (1835x768 on 3440x1440), scaled up: everything keeps the size players
  know and the view only grows sideways. Height=0 keeps native 1:1.
- Ground: 818/888 clip limits, screen centre floats and integer
  immediates, ground quad list (capped at 1749) moved with the tile list.
- 3D: cGranny ortho (+-512 x +-384, incl. HUD items) scaled; inverse
  projection 0x623a20 (1/1024, 1/768) follows the render size, which fixes
  the camera following the player ("outside world") and the missing
  player / ESC menu.
- tools: game-input.ps1 + autotest-load.ps1 drive the game (load the first
  save game, screenshots in-game and with the ESC menu).

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

128 lines
5.9 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. Native ultrawide therefore needs dxwrapper (`Dd7to9`,
D3D7 -> D3D9), see `src/mods/dxwrapper.cpp`; without it the render size is
clamped to 2048.
## 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
## 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.
- HUD: drawn in the top-left 1024x768 of the render target; 3D belt items
use the scaled HUD projection and sit off their slots.
- Activity radius: which creatures/objects are updated and animated
(`cWorld` / `cSector`, `0x00603e90` = player?, `0x005fe060` = object by id).
- 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)?
- Mouse picking (screen -> world).