- Spielen hero panel shows the selected hero in 3D (cUI_NetGranny, game/ui/granny.h). - All menu screens draw on the options' rock wall; the main menu's 1024 frame is drawn centred over it, mouse and cursor shifted to match. - The main menu's and options' own pictures and the main menu's 3D scene are left out (textures hidden by surface, scene call redirected). - Logo top left: FX_SACREDLOGO.TGA, drawn added. - Game textures by name (game/ui/texture.h), textured and added quads. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
161 lines
7.8 KiB
Markdown
161 lines
7.8 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 the loading screen 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. The main menu thread renders
|
|
the full width since 2026-10-20: its 1024x768 frame is drawn centred on the
|
|
menu screens' background (docs/screens.md, "Main menu frame").
|
|
|
|
## 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)?
|