2f4ed244ea Fix -fps logger measuring its own overhead; buffer output, fix percentiles
First real run of -fps (222 seconds, 4-monitor bench, in-mission) showed a
rock-solid 60.0 fps average but a worst frame of ~24ms in 82% of seconds, with a
median of 24.4ms and a minimum of 23.2ms. That regularity was the tell: 60Hz vsync
intervals are 16.7 / 33.3 / 50.0 ms, so 24ms is not a multiple of anything and had
no business recurring once per second with that consistency.

Cause: the logger was measuring itself. The per-second line was written straight to
the file with WriteFile on the render thread, and the frame duration is recorded
BEFORE the line is emitted -- so the write's cost landed in the *next* frame's
measurement, which belongs to the next second's bucket. Result: exactly one
inflated frame per second, indefinitely. Holding the handle open (as the first
version did) was not enough; a single WriteFile is still several milliseconds when
something like antivirus is in the path.

Three fixes:

1. Buffered output. Lines accumulate in a 128 KB static buffer (~2000 rows, about
   33 minutes at one row per second) and are flushed only when the buffer fills or
   at exit via atexit(). A normal measurement session now performs no file writes
   at all while running. Trade-off, deliberately accepted: a hard crash loses the
   un-flushed tail. gos-displays.txt remains the crash-survivable log; gos-fps.txt
   is a measurement instrument and must not perturb what it measures.

2. Meaningful percentiles. A "1% low" over 60 samples per second computes
   nFrames/100 = 0, which was clamped to 1 -- so the column was literally
   1000/worst_frame, i.e. the worst-frame column restated in different units.
   Verified against the log: worst 24.1ms -> 41.5 fps, exactly the "1% low" printed.
   Per-second is now a 5% low (worst 3 of 60), which is a number that actually
   differs from the worst frame. True whole-session 1% and 0.1% lows are computed
   at exit from a 0.5ms-bucket frame time histogram (2000 buckets, 0-1000ms) and
   printed in a new session summary along with total frames, elapsed time, average,
   and total hitches. The histogram gives exact-enough percentiles over an entire
   session without retaining every frame time.

3. Carry-over. A single frame longer than one second (a level load: the log shows
   4338ms and 4799ms frames) left the accumulator above the 1000ms threshold, so
   the next few frames each emitted their own bogus one-frame row -- visible in the
   log as rows reporting "1 frame, 1339 fps". The excess is now discarded.

Also adds #include <stdlib.h> to WinMain.cpp for atexit(); it was not reachable
through pch.hpp. GOS_FpsAtExit is declared __cdecl: GameOS builds with /Gz, which
makes __stdcall the default calling convention, but atexit() takes a __cdecl
callback -- without it VC6 rejects the call with C2664.

What the run did establish, and still stands: 60 frames per second sustained for
~150 seconds with no rhythmic multi-hitch pattern, so the mode 4 split-MFD stutter
fix (0a657b59) is holding. Genuine dropped frames (>40ms) occurred in 18% of
seconds, including clusters of 80-90ms; those are real and unaffected by this fix.
Re-measure after this change to see the true baseline.

CLAUDE.md STEP 10 updated: the previous claim that holding the handle open kept the
logger from perturbing the measurement was wrong, and is replaced with what was
actually observed.

Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-07-25 18:43:42 -05:00
2026-07-25 08:54:28 -05:00
2026-06-26 10:40:23 -05:00

BattleTech: FireStorm — workspace layout

⚠️ Setting up a new machine? Read RECOVERY.md BEFORE cloning this repo. The clone only comes out byte-exact if long paths, core.autocrlf=false, and Git LFS are configured first — cloning without them silently corrupts line endings, truncates deep VC98/MFC paths, and leaves LFS placeholders instead of the real binaries.

This is the working tree for the FireStorm total-conversion built on the MechWarrior 4 (Gameleap / GameOS) engine. Only three trees feed the build/deploy; everything else is output (regenerable), design reference, or archived clutter. See CLAUDE.md for the full build/runtime reconstruction notes.

Folder map

C:\VWE\firestorm\
├─ Gameleap\                       The MW4/Gameleap 5.03 engine + game (the two live trees)
│  ├─ code\                        ⭐ SOURCE CODE — build mw4\Code\MechWarrior4.dsw in VC6
│  │  ├─ CoreTech\                 Reusable engine layer: GameOS, MLR renderer, gosFX,
│  │  │                            GOSScript, Stuff, Network, blade + engine tools
│  │  ├─ mw4\Code\                 The game itself: MW4 lib (AI, mechlab, HUD, shell),
│  │  │                            MW4Application (→MW4.exe), MW4GameEd2 (mission editor),
│  │  │                            scriptstrings/MissionLang (string DLLs), dedicated UI
│  │  ├─ mw4\Libraries\            mw4-local libs: Adept, stlport (build first), MLR,
│  │  │                            Compost, ImageLib, gosfx, server, stuff …
│  │  ├─ mw4\Binaries\             3DS Max export plug-ins (mech/prop .erf pipeline)
│  │  └─ rel.bin\ pro.bin\         Build OUTPUT: Release (MW4.exe, MW4pro.exe) / Profile
│  │     arm.bin\ dbg.bin\         (editor MW4Ed2.exe) / Armor / Debug — regenerable
│  └─ mw4\                         ⭐ GAME DATA source tree (editor also runs here in place
│     │                            via run-editor.bat; tool exes MapCreator/Tctd/NFOEditor…)
│     ├─ Content\                  All content sources: Mechs\, Maps\, Missions\, Skies\,
│     │                            Weapons\, WeaponSubsystems\, Subsystems\, Buildings\,
│     │                            Vehicles\, Effects\, textures\, Campaigns\, Tables\,
│     │                            Defines\, ABLScripts\ (mission/AI language),
│     │                            ShellScripts\ (menu/mechlab UI), *.build manifests
│     ├─ Resource\                 Packed .mw4 packages (output of `MW4pro -build`) +
│     │                            Missions\*.nfo (MP registration) + UserMissions\
│     ├─ hsh\                      Loose 2D art loaded at runtime: mech portraits, MFD
│     │                            target images, HUD/radar bitmaps, decals, fonts
│     ├─ Assets\ Stats\            Runtime support data (cursors, world stats)
│     └─ Movies\ fonts\ Notes\     Cinematics / font sources / dev notes (not deployed)
├─ build-env\                      ⭐ TOOLCHAIN — self-contained VC6 (VisualStudio6\),
│  │                               DX 7.0a + DX Media 6 SDKs, stlnative\ headers,
│  │                               ddrawcompat\ (editor viewport fix), .reg env files,
│  │                               build/deploy scripts (see below), build logs
├─ MW4\                            Runnable game deploy (output of deploy-mw4.ps1) —
│                                  regenerable; copy this to production
├─ BTFrstrm\                       FireStorm design data: MechInfo_5.04.xls (stat source
│                                  of truth), scriptaddmech.xls (add-mech row generator)
├─ Finished HUDS from J&J\         Per-mech HUD source art (MFD + Radar) awaiting
│                                  integration, ~13 chassis
├─ _UNUSED\                        Archived clutter — nothing read by the build (EXCEPT
│                                  worth knowing: Gameleap\EditorDocs\ = original map/
│                                  terrain/NFO/ABL tutorials)
├─ CLAUDE.md                       Full build/runtime reconstruction notes (the history)
├─ README.md                       This file — orientation + fresh-machine restore
├─ RECOVERY.md                     Disaster-recovery notes
├─ ADDING-A-MECH.md                Workflow: add a new 'Mech chassis
└─ ADDING-A-MAP.md                 Workflow: add a new map/mission

The folders that matter (build inputs)

Folder Role Notes
Gameleap\code\ Source code + compiled binaries C++ engine/game source (mw4\Code, CoreTech, mw4\Libraries). Build mw4\Code\MechWarrior4.dsw in VC6. Outputs land in rel.bin\ (Release MW4.exe, MW4pro.exe), pro.bin\ (editor MW4Ed2.exe), arm.bin\/dbg.bin\.
Gameleap\mw4\ Game data source The content tree the resource packer reads (Content\, and the generated resource\*.mw4, plus hsh\, Assets\, Stats\, runtime DLLs). This is the live data set — not GameleapCode5_03\Content (that was an old 2005 duplicate, now in _UNUSED).
build-env\ Toolchain + scripts Self-contained VC6 (VisualStudio6\), DirectX 7.0a + DX Media 6 SDKs, stlnative\, the .reg env files, and the deploy scripts.

Build / deploy scripts (in build-env\)

  • deploy-mw4.ps1 — packs resources from source (build-resources.ps1) then assembles the runnable game into c:\VWE\firestorm\MW4. Trims dev bloat, ships only the canonical game packages, optimizes mech BMPs to 256-color, applies the DirectDraw compat shim.
  • build-resources.ps1 — (re)packs resource\*.mw4 from Gameleap\mw4\Content using the in-exe compiler (MW4pro.exe -build). Called by the deploy; can be run standalone.
  • deploy-editor.ps1 — installs the mission editor in place into Gameleap\mw4 (the data tree it edits): drops MW4Ed2.exe + DDrawCompat there, neutralizes the DDraw-breaking DLLs, sets the shim, writes run-editor.bat. No separate editor directory. (build-resources.ps1 moves DDrawCompat aside while the builder runs, since it's fatal to MW4pro.exe.)
  • RESOURCE-BUILD.md — how the .mw4 packaging works.

Outputs (regenerable — safe to delete and rebuild)

  • MW4\ — runnable game deploy (from deploy-mw4.ps1). Move/copy this to production.
  • The editor has no separate output dir — it runs in place from Gameleap\mw4 via Gameleap\mw4\run-editor.bat (installed by deploy-editor.ps1).

Design / reference (not build inputs)

  • BTFrstrm\ — FireStorm design data: mech stat workbooks (MechInfo_*.xls, etc.).
  • Finished HUDS from J&J\ — per-mech HUD source art (MFD + Radar).

_UNUSED\ — archived clutter (nothing here is read by the build)

Moved here to reduce confusion. Safe to delete once you're comfortable. Contents:

  • Gameleap\{Archive, Drivers, EditorDocs, Notes, batch, utilities} — historical / original-machine utility data (the build only ever used Gameleap\mw4).
  • GameleapCode5_03\{Content, hsh} — stale 2005 duplicate data trees (superseded by Gameleap\mw4).
  • resource_fullbak\ — a one-off backup of the dev resource\ set taken during the from-scratch rebuild; redundant now that the dev tree is rebuilt.

TL;DR build flow

  1. Compile code: VC6 build Gameleap\code\mw4\Code\MechWarrior4.dswrel.bin\ / pro.bin\.
  2. Deploy game: build-env\deploy-mw4.ps1MW4\ (packs resources, assembles, optimizes).
  3. (Optional) Install editor: build-env\deploy-editor.ps1 → runs in place from Gameleap\mw4 (run-editor.bat).

On a fresh Windows machine

  1. Install Git for Windows (includes Git LFS) — or Git + git-lfs separately.
  2. Enable long paths before cloning (this tree has deep VC98/MFC paths > 260 chars):
    git config --global core.longpaths true
    git config --global core.autocrlf false
    git lfs install
    
    (Also enable the OS setting: HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled = 1.)
  3. Clone to the same location so the absolute paths baked into the toolchain config resolve:
    git clone https://gitea.mysticmachines.com/VWE/firestorm.git C:\VWE\firestorm
    
    git clone pulls LFS objects automatically. If any are missing: cd C:\VWE\firestorm && git lfs pull.

After restore — make it buildable / runnable

  • Build toolchain (VC6): import the registry config (ELEVATED), per build-env\README.md:
    reg import C:\VWE\firestorm\build-env\vc6-hklm-registration.reg
    reg import C:\VWE\firestorm\build-env\vc6-directories.reg
    
    Then build Gameleap\code\mw4\Code\MechWarrior4.dsw (see CLAUDE.md STEP 1/3), or just use the already-mirrored Gameleap\code\rel.bin / pro.bin binaries.
  • Run the game: build-env\deploy-mw4.ps1 reassembles C:\VWE\firestorm\MW4 and applies the Win11 DirectDraw compat shim; or run the already-mirrored C:\VWE\firestorm\MW4\MW4.exe after re-applying the shim (AppCompat is keyed on exe path, so a new machine needs it re-set — the deploy script does this).
  • Editor: Gameleap\mw4\run-editor.bat (installed in place; see CLAUDE.md STEP 9).

Notes

  • core.autocrlf=false — line endings are preserved byte-for-byte; keep it that way.
  • The mirror includes machine-specific bits (registry exports, deploy AppCompat). They restore the files, but registry/AppCompat state must be re-imported on the new machine (steps above).
  • See CLAUDE.md for the full build/runtime reconstruction history and README.md for the layout.
S
Description
No description provided
Readme
9.5 GiB
Languages
C 48.8%
C++ 36.9%
Rich Text Format 12.2%
HTML 0.6%
Visual Basic 6.0 0.5%
Other 0.5%