Crusader_Decomp/docs/regret-game-start.md
2026-03-30 00:19:01 +02:00

10 KiB

No Regret Game_Start Flow

Scope

This note documents the currently opened live REGRET.EXE startup lane around Game_Start and the later helper that performs the actual new-game mission hop.

The immediate goals of this pass were:

  • name the directly involved startup helpers in Ghidra
  • annotate the startup-state globals that feed the new-game path
  • explain why No Regret appears to set the start map twice

Named Functions

The following previously-unnamed functions are now promoted in the live REGRET.EXE database.

Address New name Why the name is justified
1058:0bbe HandleCommandlineArgs Matches the already-recovered Remorse-side parser role. It compares argv entries against a startup-option table and writes g_warpToLevelNoArg, the difficulty override, map offset, egg override, and the extra startup-video flag.
1030:032d Game_RunNewGameFlow This is the full No Regret new-game helper. It plays the ORIGIN/ANIM01 intro lane, opens the difficulty modal when not warping, rebuilds the world/avatar state, and then performs the actual mission-start teleporter hop or the debug -warp path.
1030:022e Game_DrawCenteredStartupSplash Called immediately before the teleporter hop in both the normal and -warp startup lanes. It fades black, clears the active gameplay state, centers one startup screen object, and redraws the viewport.
11c8:0d96 Game_EnterFrontendMenuViewport Used before modal-gump dispatch. It clears the gameplay-style viewport flag, expands the mouse Y range to the taller menu height, and refreshes the screen object.
11c8:0d39 Game_RestoreGameplayViewport Used after the modal/front-end phase. It restores the gameplay-style viewport state, notifies NPC update lanes, restores the shorter mouse Y range, and refreshes the screen object again.

Game_Start Structure

Game_Start in No Regret still looks like the top-level frontend entry point.

High-level shape:

  1. Freeze the current gameplay/process state enough to enter a frontend/modal lane.
  2. Enter the taller frontend viewport mode and dispatch the startup modal gump.
  3. Handle non-new-game modal results such as exit/other menu actions.
  4. For the new-game path, clear world/gameplay state, rebuild the avatar and starter items, and do an early teleporter hop to map 1, egg 0x1e.
  5. Fade/reset again and hand back into the wider startup framework.

The key early selector is still:

  • 1008:1446 PUSH 0x1
  • 1008:1448 PUSH 0x1e0001
  • 1008:144e CALLF 0x1030:0000

That early site is real, but it is not the only startup teleporter call that matters.

Actual New-Game Mission Start

The function now named Game_RunNewGameFlow is the important second lane.

It is called from at least two concrete places:

  • CreditsProcess_Run at 1008:0056
  • another startup/control lane at 1058:0a2a

That already shows it is not dead duplicate code. It is a standalone startup helper reused by the frontend.

Its normal no-warp branch does all of the following in one place:

  1. Play the ORIGIN/ANIM01 intro-video lane.
  2. Open the difficulty-selection modal.
  3. Reinitialize NPC/world/tracker state.
  4. Apply any command-line difficulty override.
  5. Recreate starter inventory items.
  6. Draw the centered startup splash.
  7. Perform the actual mission-start teleporter hop.

The effective mission-start selector is:

  • 1030:05c3 PUSH 0x1
  • 1030:05c5 PUSH 0x1e0001
  • 1030:05cb PUSH CS
  • 1030:05cc CALL 0x1030:0000

This is the selector that redirected real new games only after the script was updated to patch it together with the earlier Game_Start site.

Startup Globals

These data roles are now annotated in the live Ghidra session.

Address Current best role
1480:1453 Startup-world transition flag. Game_Start clears it before the early menu-start teleporter hop. Game_RunNewGameFlow sets it during world/NPC rebuild and clears it again immediately before the actual mission-start teleporter hop.
1480:0ac8 Optional command-line warp X override. 0xffff means no direct coordinate warp is requested.
1480:0aca Optional command-line warp Y override.
1480:0acc Optional command-line warp Z override.
1480:0ace Command-line difficulty override parsed by HandleCommandlineArgs. If greater than zero, the new-game flow copies it into g_difficultyLevel before the teleporter hop.
1480:0ad0 Command-line -mapoff additive map offset used by the debug -warp path.
1480:0ad2 Command-line egg override used by the -warp path. When negative or 0x001e, the code falls back to the default startup egg.
1480:0ad4 Extra startup-video behavior flag parsed by HandleCommandlineArgs. Clear means the ORIGIN/ANIM01 lane is played once; set means the same lane is repeated until 1480:95f0 becomes nonzero.
1480:95f0 Startup-video loop exit latch used by Game_RunNewGameFlow and CreditsProcess_Run.

Command-Line Warp Controls

The startup parser exposes more real gameplay-side command-line controls than just -warp.

Currently proven from the recovered parser strings and control flow:

  • -warp
  • -skill
  • -mapoff
  • -egg
  • -debug
  • -demo
  • -setver
  • -asylum

The same executables also contain the runtime status strings:

  • Warping to mission %d.
  • Warping to mission %d @ x:%d y:%d z:%d.
  • Defaulting to skill level %d
  • Destination Egg = %d

That parser behavior is now tight enough to promote the actual syntax:

  • -warp <mission> = mission-only manual warp
  • -warp <mission> <x> <y> <z> = direct coordinate warp

The key parser detail is that there are no separately recovered -x, -y, or -z switches in this lane. After HandleCommandlineArgs parses the mission number, it checks the next argv token. If the next token is missing or begins with -, the parser takes the mission-only path and prints Warping to mission %d.. Otherwise it consumes the next three argv tokens as X, Y, and Z and prints Warping to mission %d @ x:%d y:%d z:%d..

There is one important precedence rule in Game_RunNewGameFlow: -egg wins over the coordinate override path. When X is present but the egg override is nonnegative, the code still routes back into the egg-based teleporter lane. The direct NPC_Teleport path only runs when X/Y/Z are present and the egg override is still negative.

The live REGRET.EXE table backing that -warp mission branch is now closed directly too.

In Game_RunNewGameFlow, the target map comes from:

mapno = *(int *)(g_warpToLevelNoArg * 2 + 0x75c) + DAT_1480_0ad0;

So the current retail No Regret mission-table base is 1480:075c.

Static bytes at that address:

1480:075c: 00 00 01 00 03 00 05 00 07 00 09 00 0b 00 0d 00
1480:076c: 0f 00 11 00 13 00 15 00 17 00 19 00 1b 00 1d 00
1480:077c: 28 00

Interpreted as little-endian words, the current recovered base-map table is:

  • 0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 40

That is the same 17-word sequence already recovered from No Remorse at 1478:0488, so the two games currently match on the embedded mission-to-base-map table even though the surrounding startup control flow differs.

Could Parameters Replace The Executable Hack?

For a normal fresh game, no.

The executable patch was needed because the stock new-game path still hardcodes the startup selector payload (map = 1, egg = 0x1e) in code. No Regret does that twice, once in Game_Start and once again in Game_RunNewGameFlow.

The command-line warp lane is different:

  • it only runs when g_warpToLevelNoArg != -1
  • it uses a debug/manual warp flow rather than the ordinary fresh-game path
  • it computes the target map from an embedded mission-to-map table plus -mapoff
  • it can also change the destination egg
  • it can apply explicit X/Y/Z destination overrides through -warp <mission> <x> <y> <z>

So a command line could likely have reproduced the practical destination change for a manual debug warp session, but not the ordinary fresh-game mission-1 behavior that the patch script changed.

For maps with no usable teleport egg, the parameter-only workaround is now clearer too: use -warp <mission> <x> <y> <z> together with -mapoff, and do not pass -egg. That path bypasses the egg lookup and calls NPC_Teleport directly. The current inspected teleport lane does not show any automatic snap-to-ground rescue, so this only works if the supplied coordinate triple is already valid for the target map.

Current best distinction:

  • executable patch = changes the stock fresh-game startup behavior
  • command-line warp args = invoke a separate manual/debug startup path that can land on a different mission/map/egg and likely a different coordinate triple

Likely Reason Map 1 Is Set Twice

The most likely explanation is that No Regret preserved two valid entry paths into an in-engine gameplay state instead of refactoring them into one shared startup selector.

Evidence for that read:

  • Game_Start contains an early teleporter hop immediately after rebuilding the avatar and starter items.
  • Game_RunNewGameFlow is a separate, live helper called from CreditsProcess_Run and another startup/control lane.
  • Game_RunNewGameFlow owns the full intro-video, difficulty-modal, world-reset, and real mission-start sequence.
  • Both paths call the same local teleporter wrapper with the same literal (1, 0x1e, 1) payload.

Current best model:

  • the early Game_Start hop is a frontend/bootstrap entry into a playable engine state
  • the later Game_RunNewGameFlow hop is the authoritative mission-start step for a real new game after the intro/difficulty flow
  • both kept their own literal selector instead of sharing one startup-map global or helper constant

So the duplication looks more like split control-flow ownership and startup code reuse than a deliberate “set it twice just in case” pattern.

Practical Patch Implication

For No Regret, changing the fresh-game destination map must update both hardcoded selectors:

  • early Game_Start selector at 1008:1448
  • actual mission-start selector at 1030:05c5

Patching only the early site is insufficient because the later new-game helper can silently overwrite that choice during the real mission-start sequence.