# Photo Mode ? proposal and research notes **Status: shelved 2026-08-09. Nothing implemented. This is a design record so the research does not have to be repeated.** Goal: produce clean head-on renders of every 'Mech, on a solid background with no UI, to use as source art for the MFD / Radar / print damage paper dolls. Every source reference below was verified against the tree on 2026-08-09. ## Why this is cheap: the capture already exists The game already renders the mech alone, on a solid background, at stepped rotations, and reads it back out of the frame buffer. It is the **MechView** feature, and it ships enabled behind a switch. Photo mode is that same code writing files instead of a texture. `-help` documents it (`MW4Application.cpp` around line 919): ```text -mechview <1|2> Show the rotating mech view. 1 = on the radar -mv <1|2> screen, 2 = on the main screen. Default: off. ``` What it does today: on entering MechLab it bakes a **25-frame turntable** of the current mech into a 2048x2048 atlas of 360x360 cells (5x5, so 14.4 degrees per step), then flips through the cells to animate a rotating mech on the pod's radar monitor or on the main screen. It is pre-rendered because drawing live 3D onto a *secondary* DirectDraw device every frame is not practical ? the same constraint behind all the MFD work. Authored by "jcem", same era as the other CTCL cabinet features. ## The machinery, with verified locations | Piece | Where | Notes | |---|---|---| | Per-frame render hook | `CoreTech/Libraries/GameOS/WinMain.cpp:1215` | `if (g_pfnCTCL_AfterBeginScene) { (*g_pfnCTCL_AfterBeginScene)(); }` ? called right after `BeginScene` | | Hook pointer | `WinMain.cpp:43` | `void (__stdcall *g_pfnCTCL_AfterBeginScene)() = NULL;` ? free unless CTCL installs it | | The bake (our template) | `mw4/Code/MW4/MW4Shell.cpp:14857` | `CTCL_AfterBeginScene()` ? the whole capture loop | | The playback | `MW4Shell.cpp:14936` | `CTCL_UpdateMechView()` ? swaps in after the bake | | Hook install | `MW4Shell.cpp:1705` | `g_pfnCTCL_AfterBeginScene = CTCL_AfterBeginScene;` | | Exclusivity assert | `MW4Shell.cpp:1623` | `gosASSERT(!g_pfnCTCL_AfterBeginScene);` ? only one owner allowed | | Solid-colour clear | `CoreTech/Libraries/GameOS/3DRasterizer.cpp:1044` | `gos_SetupViewport(bool FillZ, float ZBuffer, bool FillBG, DWORD BGColor, ...)` ? background colour is already a parameter; CTCL passes `0x00000000` | | Mech-only draw | `mw4/Code/MW4/MechLab.cpp:1598` | `MechLab::UpdateDisplay()` is just `s_MechCamera->DrawScene(true)` ? **no UI is drawn** | | Rotation | `MechLab.cpp:257` `Execute()` | `MechLab.cpp:285` is the `yaw += 0.02f` spin; `MechLab.cpp:275` gates an absolute-yaw branch on `g_pfnCTCL_AfterBeginScene == CTCL_AfterBeginScene` | | Chassis count | `MechLab.cpp:1185` | `MechLab::GetChassisCount()` | | Chassis swap | `MechLab.cpp:1199` | `MechLab::SetMech(int mech_id, const char *mech_name, const char *path)` | | MechLab construction | `MW4Shell.cpp:4693` | `MechLab::Instance = new MechLab;` | | Camera | `MechLab.cpp:175` | `SetPerspective(1.0f, 1100.0f, Stuff::Pi_Over_3)`, full viewport | | Atlas dimensions | `CoreTech/Libraries/GameOS/render.cpp:1443` | `m_ptMechViewDim` 2048x2048, `m_ptMechViewSize` 360x360 | | Mode global | `CoreTech/Libraries/GameOS/VideoCard.cpp:64` | `g_nMechViewType` ? 0 off, 1 radar screen, 2 main screen | | Switch parsing | `MW4Application.cpp:1575` | `-mechview ` / `-mv ` | The capture loop, abridged from `CTCL_AfterBeginScene`: ```cpp for (int qq = 0; qq < nCount; qq++) { gos_SetupViewport(1, 1.0, 1, 0x00000000, 0.0, 0.0, 1.0, 1.0); // clear to solid colour MechLab::Instance->Execute(); // step to an absolute yaw MechLab::Instance->UpdateDisplay(); // draw ONLY the mech BackBufferSurface->Lock(&rcSource, &desc2, DDLOCK_..., NULL); // read back a centre crop /* memcpy into the atlas cell */ BackBufferSurface->Unlock(&rcSource); } ``` Also noted: `MechView_Before()` / `MechView_After()` at `MechLab.cpp:97` and `:102` toggle `ClearBackBufferBeforeDraw`. They are **defined and never called** anywhere in the tree ? dead helpers. They are an alternative route to the same clear if the viewport approach proves awkward. ## Chosen design **A fully automatic batch mode. No hotkeys, no new UI, no interaction.** ```text MW4.exe -photoall ``` Boots, writes `photos\.bmp` for all 65 chassis, exits. Optional later: `-photoall ` for an n-step turntable per chassis instead of a single head-on frame. ### Rejected: a new MechLab tab MechLab tabs are defined in `Content/ShellScripts/MechBay/*.script` **plus** code callbacks (`ML_CallbackHandler`, the `MLDataSetup*` IDs). A new tab means script authoring, new callback IDs and layout work ? and the tab's own UI would be on screen during capture, which is the exact thing being avoided. It would also ship to players who have no use for it. ### Rejected: interactive hotkey as the primary interface A `-photo` switch plus F9/F10 in MechLab is feasible ? `gos_GetKeyStatus()` is usable from shell code (see `MW4Shell.cpp:11678`) ? but it needs one manual session per chassis. Worth adding later as a supplement for reshooting a single mech; not the main path. ### Getting into MechLab without navigating menus Two routes: 1. **Drive the shell.** Add a `PhotoShellStart` to the existing `shellStart` enum; there is precedent (`InstantActionShellStart`, `CampaignShellStart`, `CampaignLoadShellStart`). Fits the existing design but requires understanding the shell state machine. 2. **Bypass the shell.** Construct `MechLab` directly the way `MW4Shell.cpp:4693` does, loop, capture, exit. A batch art tool needs no shell screen at all, so this is smaller, more predictable, and cannot get stuck on a dialog. Same shape as the editor's headless `-report`. **Recommendation: route 2.** ## Implementation plan | # | File | Work | |---|---|---| | 1 | `MW4Application.cpp` (~1575, beside `-mechview`) | parse `-photoall` (+ optional step count); set globals | | 2 | `MechLab.cpp:275` | widen the absolute-yaw condition so photo mode also sets an exact angle instead of the `+= 0.02f` spin | | 3 | new `Photo_AfterBeginScene()` | modelled on `CTCL_AfterBeginScene`; explicit angles rather than the 5x5 grid maths, configurable crop, writes files | | 4 | install into `g_pfnCTCL_AfterBeginScene` | assignment only; `WinMain.cpp:1215` already calls it | | 5 | **BMP writer** | **the only genuinely new code, ~40 lines.** R5G6B5 or X8R8G8B8 to 24bpp BMP. Nothing in the tree writes images ? verified, there is no screenshot facility anywhere | | 6 | `-help` | document the switch | ## Open questions ? need a real run to settle 1. **Framing per chassis.** The camera is fixed (`SetPerspective(1.0f, 1100.0f, Pi_Over_3)`, full viewport) with the mech at the scene root. A Flea and an Atlas will not fill the frame equally. Probably acceptable since each mech gets its own 512 sheet, but if consistent scale is wanted, derive a per-mech camera distance from the mech's bounding box. 2. **Which yaw is head-on.** `yaw = 0` gives the model's native orientation; whether that is front, back or side has to be eyeballed once and then hard-coded. ## Constraints - **Run windowed.** Fullscreen is `bitdepth=16` (R5G6B5). Windowed sets `bitDepth = DesktopBpp` = 32. Bigger window gives a bigger capture, up to the engine's 4:3 maximum of 1600x1200 (`ImageHlp.cpp` asserts the supported mode list). 1200px tall beats the 1024 the original art pipeline started from. - **Must not clash with `-mechview` / CTCL.** `MW4Shell.cpp:1623` asserts the hook is unowned; both cannot install it. Photo mode should refuse to arm when MechView or CTCL is active. - Requires a Windows VC6 rebuild to test; none of this is verifiable on Linux. ## Possible phase 2: per-component capture The mech is an `ElementRenderer` element with sub-elements per body part. If per-part visibility is reachable, photo mode could capture **each zone separately** and hand over the exploded pieces directly ? turning the paper-doll pipeline from "capture, then cut up by hand in an image editor" into "capture, done". Not investigated. This is the difference between a convenience and a genuine step change, so it is worth checking the element hierarchy before finalising the design. ## Why this was wanted From the MFD/Radar mapping work on 2026-08-09 (see `MFD-RADAR-MAPPINGS.md`): - **The Urbanmech has no damage art at all.** Its external MFD, radar and cockpit-atlas bitmaps are byte-identical copies of the Uller's; only its mechlab portrait is genuine, and that is a cropped three-quarter view unusable as a paper-doll source. It is the only unintentional placeholder in the roster (Behemoth II sharing Behemoth's art is deliberate). Fixing it needs new source renders ? exactly what photo mode would produce. - **Several chassis have crude MFD sheets.** `thor`, `loki`, `bushwacker`, `solitaire`, `chimera`, `cougar` all sit in the 0.85?0.90 fit band with loose boxes. The Atlas was regenerated from its Radar art (0.795 -> 0.956), but that trick only works where good Radar art exists. Photo mode would remove that dependency.