Files
firestorm/MISSION-TIME-LIMITS.md
T
CydandClaude Opus 5 3514acf6d9 Extend MP mission time limit to 240 min (LYLT)
Time-limit pick lists go from 9 entries (max 30 min) to 23:
1-15, 20, 25, 30, 45, 60, 120, 180, 240. Default stays 7 min.

Content + console only, no MW4.exe rebuild: nothing between the
dropdown and the mission timer clamps the value, and m_gameLength is
8 bits on the wire (MWApplication.cpp:790), so 255 min was always the
real ceiling.

- ConLobbyMission.script (venue console) + HostLobbyMission.script
  (PC host lobby): new list, max_displayed = 10 on the time dropdown,
  drop_list_size[5] 9 -> 23, and the hardcoded 9 loop bounds plus the
  nselected = 3 fallbacks updated.
- TIME_LIST_DEFAULT is now the literal 6 (index of 7 min) instead of
  the C++ global g_nTimeList_Index = 3: index 3 is 4 min in the new
  list, and changing the global would have forced an exe rebuild. The
  reset-to-defaults path matches by VALUE (g_nTimeList_Value = 7) and
  needed no change.
- MISSION-TIME-LIMITS.md: value flow, display formats, why 0 (no time
  limit) black-screens the networked HUD as shipped, and the Plan B
  outline for going past 255.

Sources only: the repacked resource\*.mw4 packages, their .dep files,
the deployed MW4\resource copies and the staged MW4pro.exe are build
output and are NOT tracked here - rebuild them from main after this
merges (build-env\build-resources.ps1).

Verified locally against the built packages: FSConsole self-test green
against vpod_ctcl.py; the console launched windowed on the dev box with
the list scrolling 1 -> 240 and 240 selectable. Not yet run against a
real bay.

FSConsole's own changes (23-entry catalog, NmpBuilder 1-255 guard,
SelfTest.CheckTimeLimits) live in the still-untracked venue/ tree, so
they are NOT in this commit either.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 10:47:14 -05:00

9.3 KiB

Mission time limits — how long a match can be set for

Reference for the MP mission time limit (m_gameLength): what the operator can pick, what the engine can carry, why "No Limit" is not currently usable, and what it would take to go past 255 minutes. Written alongside the LYLT branch, which extended the pick lists to the engine's existing ceiling.

The short version

Value
Console / PC lobby list (after LYLT) 1-15, 20, 25, 30, 45, 60, 120, 180, 240 min
Wire ceiling, no code change 255 min (4 h 15 m)
Default 7 min
0 on the wire "no time limit" — do not ship; see below
Beyond 255 needs a wire-format change — see Plan B

How the value flows

  1. The operator picks minutes from a dropdown in ConLobbyMission.script (venue console) or HostLobbyMission.script (PC host lobby). FSConsole reads the same list from venue/fsconsole/content/FSConfig.xml.
  2. The script calls SetNetworkMissionParamater(GAME_LENGTH_PARAMETER, minutes)params->m_gameLength (MW4Shell.cpp:2066). No clamp anywhere on this path.
  3. The value ships to the pods inside the NMP blob as 8 bits: stream->WriteBits(&m_gameLength, 8); // 256 minutes (MWApplication.cpp:790, read back at :910). That is the entire ceiling: 0-255.
  4. At mission start the pod does mission->SetEndMissionTime(m_gameLength * 60.0f) (MWApplication.cpp:3537) — and only when m_gameLength > 0 — plus CTCL_SetGameTime(m_gameLength * 60) for the console clock (int32 seconds, no limit of its own).

Nothing between the dropdown and the mission timer cares how large the number is. The mission timer itself is double (Stuff::Time, Scalar.hpp:225), so long matches do not lose time accuracy; only the Scalar (float) countdown view does, and only by ~8 ms at the 24-hour mark.

Displays are already wide enough

Where Format At 240 min
MP scoreboard clock, hudscore.cpp:443 %d:%02d 240:00
Radar/shell HUD, GUIRadarManager.cpp:1981 %02d:%02d 240:00 (6 glyphs in a slot laid out for 5 — watch for clipping)
Script GetGameTime, MW4Shell.cpp:3910 %02d:%02d:%02d 04:00:00
FSConsole pane, FSGamePane.cs <min>:00 240:00

What LYLT changed

Content and console only — no MW4.exe rebuild:

  • ConLobbyMission.script: TIME_LIST_COUNT 9 → 23, the 23 list entries, and max_displayed = 10 for the time dropdown so it scrolls instead of overflowing the panel. TIME_LIST_DEFAULT was $$g_nTimeList_Index$$ (the C++ global g_nTimeList_Index = 3, MW4Shell.cpp:159) — index 3 is "7" in the OLD list but "4" in the new one, so it is now the literal 6, the index of "7". Changing the C++ global instead would have forced an exe rebuild for no other gain. The "reset to defaults" path is unaffected: it searches the list by value for TIME_VALUE_DEFAULT = g_nTimeList_Value = 7.
  • HostLobbyMission.script: same list, drop_list_size[5] 9 → 23, max_displayed = 10, and the two selection loops that hardcoded 9 (and the nselected = 3 fallback → 6).
  • FSConsole: the 23 entries in catalog/generate_fsconfig.py (regenerated into content/FSConfig.xml), MaxDropDownItems = 10 on the pane's combo, and a range guard in NmpBuilder — it used to write TimeLimitMinutes & 0xFF, which silently turned 256 into 0. It now throws outside 1-255. SelfTest.CheckTimeLimits() proves every catalog entry encodes to the right value at the right bit, and that 0/256/1440 are refused.

Anything already in flight is unaffected: a stored value picks the first list entry >= it, so old saved settings still resolve.

The branch carries sources only. The repacked resource\*.mw4 packages, their .deps, the deployed MW4\resource copies and the staged MW4pro.exe are build output and are not tracked on it — they get rebuilt from main once this merges. Until you repack, a checkout still shows the old 9-entry list in game:

build-env\build-resources.ps1                       # rebuilds core.mw4 + props.mw4
copy Gameleap\mw4\Resource\props.mw4 MW4\resource\  # and core.mw4

The "No Limit" trap — why 0 is not shipped

SetEndMissionTime is only called when m_gameLength > 0, so 0 looks like a free "unlimited match" option. It is not usable as the code stands:

  • With the end timer stopped, GetEndMissionTime() returns 0 (MWMission.hpp:602-608), so CheckEndMissionTime() returns 0 too.
  • The networked fade-out reads that as "under 3 seconds left" and fades the screen to full black three seconds into the mission (MWGUIManager.cpp:1633-1649).

Nothing sets 0 today (CTCL_DefaultHostSetup forces 7, MW4Shell.cpp:13332), which is consistent with the path never having been exercised. Read from code, not yet reproduced in game.

To make "No Limit" real: guard that fade block on EndMissionTimerRunning(), then audit every other consumer that treats a 0 countdown as "expired" (TestForGameShutDown, the 60 s/30 s voice cues at MWMission.cpp:1263-1288, scoreboard and mission-review). That is an MW4.exe rebuild plus a pod redeploy — cheap in code, but it needs the same lockstep rollout discipline as Plan B, and the venue gets most of the benefit from 240 minutes anyway.

Plan B — going past 255 minutes

Everything above is configuration. Past 255 it becomes a wire-format change, which is a different risk class: the NMP has no version field, so a pod on a different build mis-parses every field after m_gameLength (map name, MapClientCRC) and refuses the lobby.

B1 — widen the field. WriteBits/ReadBits for m_gameLength (MWApplication.cpp:790 and :910). Use 11 bits (2047 min): the blob is 3777 fixed bits with 7 pad bits before WriteByteAlign, so up to 15 extra bits are absorbed without changing the 473-byte fixed length, and FSConsole's length assert keeps working. The bit layout still shifts — that is what forces B3.

B2 — update every mirror, identically.

  • mw4dummy.cpp:433/552mw4print carries a full duplicate of the serialization for score printouts. Miss it and printouts garble silently.
  • venue/fsconsole/protocol/NmpBuilder.cs (+ testdata/vectors/expected.json, the golden cross-validation vectors, and the CheckTimeLimits bit offset).
  • Rebuild MW4.exe Release and Profile (Profile is the resource builder).

B3 — lockstep deployment. All pods, both consoles (legacy MW4.exe console and FSConsole) and mw4print ship together. The …A7 legacy-console rollback path is only valid against equally rolled-back pods.

B4 — display formats. %d:%02d and %02d:%02d print 1440:00; both want an h:mm:ss form above 60 minutes, with column and clip re-checks. FSConsole's <min>:00 likewise. MW4Shell::GetGameTime already emits hh:mm:ss.

B5 — the actual unknown: a day-long match. The format change is a day's work; proving the engine survives 24 hours is the project. In order: memory growth and heap fragmentation (debris, effects, chat, net-stat buffers); the .mr mission-review recording, which accumulates per frame — establish whether it has a cap or rollover before assuming a 24 h recording is even writable, and what that does to the printout pipeline; DirectPlay session stability; score and stat counters; and the 8-bit sibling timers — m_joinInProgressCutOffTime is also 8-bit minutes (the game writes 1000 and wires 232), so join-in-progress cutoff cannot express a long match either.

B6 — test ladder. vpod bench → 4 h bay run → 12 h → 24 h, sampling process memory and confirming end-of-mission, review and printout at each rung. Expect the failures in B5, not B1.

Verifying a time-limit change

  1. python venue\fsconsole\catalog\generate_fsconfig.py — regenerates FSConfig.xml and fails loudly if the authored blocks drift from the sources.
  2. dotnet publish venue\fsconsole\FSConsole.csproj -c Release -o dist\App, then FSConsole.exe -selftest -mission -site bench.ini against python C:\VWE\showrunner\tools\vpod_ctcl.py --pods 2 --cam --write-ini bench.ini. CheckTimeLimits runs in every self-test mode, network or not.
  3. build-env\build-resources.ps1 to repack props.mw4, then copy it to MW4\resource.
  4. In game: pick the longest entry, confirm the lobby dropdown scrolls, the HUD clock reads 3-digit minutes without clipping, the mission ends on time, and the debriefing prints.