Files
firestorm/PHOTOMODE.md
T
42a66157ea Add PHOTOMODE.md: shelved design for automatic mech photo capture
Records the research behind a proposed capture mode for producing clean head-on
mech renders as source art for the damage paper dolls. Nothing is implemented;
this exists so the investigation does not have to be repeated.

The key finding is that the capture already exists and ships. MechView
(-mechview / -mv, documented in -help) bakes a 25-frame turntable of the
MechLab mech into a 2048x2048 atlas and plays it back on the pod's radar screen.
That bake renders the mech alone, on a solid background, at stepped rotations,
and reads it out of the back buffer - which is exactly the capture wanted. It
just writes a texture rather than files, and is gated behind CTCL.

Two things confirmed from source that remove the main uncertainties:

  - MechLab::UpdateDisplay() is only s_MechCamera->DrawScene(true), so the
    capture draws no UI at all; and gos_SetupViewport already takes the
    background colour as a parameter.
  - MechLab exposes GetChassisCount() and SetMech(), so a batch mode can walk
    the whole roster without any interaction.

Proposed shape is "MW4.exe -photoall": boots, writes photos\<chassis>.bmp for
all 65 chassis, exits. No hotkeys or new UI. A new MechLab tab was considered
and rejected - tabs need shell-script plus callback work, and the tab's own UI
would be on screen during the capture. Recommended route into MechLab is to
bypass the shell and construct it directly, as MW4Shell.cpp:4693 does, rather
than adding a shellStart mode.

The only genuinely new code identified is a ~40 line BMP writer; the tree has
no screenshot facility at all. Everything else is wiring together routines that
already run in the shipped game.

Open questions needing a real run are recorded: per-chassis camera framing, and
which yaw is actually head-on. Constraints noted: run windowed for 32-bit colour
(fullscreen is bitdepth 16), and refuse to arm when -mechview or CTCL owns the
render hook, since MW4Shell.cpp:1623 asserts a single owner.

All 21 source references in the document were verified against the tree.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-08-09 14:04:42 -05:00

9.2 KiB

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):

-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:

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.

MW4.exe -photoall

Boots, writes photos\<chassis>.bmp for all 65 chassis, exits. Optional later: -photoall <n> 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.