Files
goldeneye-pc-port/docs/HANDOFF-ai-xmacro.md
T
James Dansereau 9d118e0b13 M-13: 21-level sweep matrix + D123 (C1 fix) + D124 (C2 Jungle fix)
WS4 level sweep driven to completion: 12/21 solo levels PASS
(load + render + no-crash). Full matrix in docs/LEVEL-STATUS.md,
reusable runner tools_pc/level_sweep.sh, WS6 checklist docs/LEVEL-PLAYTEST.md.

Fixes:
- D123 (tools_pc/d88_propdefs.py): crash class C1 on 6 levels
  (Dam/Runway/Frigate/Statue/Streets/Cradle). D122 fallout - the widened
  Vehichle/AircraftRecord.ailist slot was zeroed instead of carrying its
  read-before-write integer AI-list id, so prop.c -> ailistFindById(0) ->
  GAILIST_AIM_AT_BOND -> ai() ran a CHR aim list with a NULL chr ->
  chrIsNotDeadOrShot(NULL). New OBJ_ID_WORDS arm. 4 levels -> PASS,
  Runway falls through to C2. BUNKER1/Silo unregressed.
- D124 (port/src/gimgfixup.c): crash class C2 on Jungle. Compiled
  explosion-DL pointer-sync keyed on an IMAGESEG marker that texLoad()
  had already erased -> compiled globalDL_0xNNN arrays kept link-time
  IMAGESEG words -> 0xABCDxxxx into fast3d on first explosion draw.
  Latent on every level. Detect the slot from the compiled array instead.

Diagnosed, NOT fixed (write-ups in PCPortResearch.md SF):
- C2-GDL (D124-Facility addendum): Facility/Runway - model texture-pool
  path yields a garbage tex->data; every model file loads at an odd
  bank base (mempAllocBytesInBank does no alignment). Suspect
  d43_emit.py/pcmodels sidecar texture-blob offsets (N64 8B vs PC 16B Gfx).
- C3/C6 (D125): Aztec/Bunker2/Surface2 - converted propDefs blob in RAM
  doesn't match d88_propdefs.py offline output (garbage after record 0);
  runtime sizepropdef() walk drifts ~100 records -> wrong door/modelid.
  d88_propdefs.py convert_stream + sizepropdef() PORT strides verified
  correct; suspect a dest-offset/pd_end bug in d88_emit.py propdefs emit.

Remaining: 9 crashes / 6 classes (C2-GDL, C3/C6, C2m Jungle G_MTX,
C5 Control BG portal, C4 Depot BG tile/room, C7 Surface1 sndSetupSound).
Priority order in docs/LEVEL-STATUS.md.

Also folds in the in-progress docs restructure (HANDOFF/AGENTS/CLAUDE,
PCPortResearch D121/D122 SF/SH entries, PLAN-linear-level-sweep,
HANDOFF-ARCHIVE, BACKLOG).
2026-08-29 16:36:18 -04:00

7.4 KiB
Raw Blame History

RESOLVED — historical. D7 closed at the compile+link milestone. Kept for the AI X-macro analysis. Not active work.

HANDOFF — AI X-macro system (D7), the last build blocker

Status

Full ge007 build: 224 objects compile; only chraidata.c fails. All other .c files build clean. Findings D1–D6 are resolved and recorded in docs/PCPortResearch.md §11. D7 (this file) is the remaining blocker.

Build command:

export PATH="/c/msys64/mingw64/bin:$PATH"
cd build-pc && cmake --build . --target ge007 2>&1 | grep -A3 -i "chraidata\|aicommands\|bondaicommands"

The failing file and its includes

src/game/chraidata.c includes (lines 14–17):

#include <ultra64.h>
#include <bondgame.h>
#include <bondaicommands.h>   // <- pulls in the whole AI X-macro system
#include "chrobjdata.h"

bondaicommands.h includes:

  • bondconstants.h (which at line 17 includes <CPPLib.h>, and at line 731 includes "aicommands.def" to build the AI_CMD enum)
  • aicommands2.h (line 864) — a pre-generated header (from tools/cmdbuilder.c), already preprocessed.

So the failing path is the raw aicommands.def include (bondconstants.h:731) plus the bondaicommands.h macros (isSubroutine, SWITCH, CALL, …) that chraidata.c uses directly.

Exact error chain (verified by rebuild)

Trigger: CALL(GAILIST_PLAY_IDLE_ANIMATION) at chraidata.c:114 (and similar at 123, 126, …). The chain:

  1. CALL(...) expands to code that calls isSubroutine(<ID>).
  2. isSubroutine(ID) (bondaicommands.h:275–284) contains:
    #define isSubroutine(ID) ((ID == GAILIST_PLAY_IDLE_ANIMATION) |\
                                (ID == GAILIST_BASH_KEYBOARD) | \
                                (ID == GAILIST_ATTACK_BOND) | \
                                (ID == GAILIST_RUN_TO_BOND) | \
                                (ID == GAILIST_STARTLE_AND_RUN_TO_BOND) | \
                                (ID == GAILIST_WAIT_ONE_SECOND) \
                                IF(NOT(DEFINED(SETUPSUBROUTINES(ID))))\
                                (\
                                    | SETUPSUBROUTINES(ID)\
                                ))
    
  3. DEFINED(x) (CPPLib.h:279–280):
    #define DEFINED(x) _DEFINED(x)
    #define _DEFINED(x) IS_PROBE(CAT(_DEFINED, _##x##_))
    
    With x = SETUPSUBROUTINES(ID), the ## pasting _##x##_ tries to paste _ against SETUPSUBROUTINES(ID) — a macro call (multiple tokens), not a single pp-token. GCC hard-errors:
    bondaicommands.h:281:63: error: pasting ')' and '_' does not give a valid
    preprocessing token
    
  4. CPPLib.h:287 has a special case that was meant to handle this:
    #define _DEFINED_SETUPSUBROUTINES(ID) PROBE(~),
    
    i.e. the intent is that _##SETUPSUBROUTINES(ID)##_ → _DEFINED_SETUPSUBROUTINES(ID)_ → the defined macro above. But GCC's ## requires each side to be a single pp-token, so it never reaches that special case. IDO's preprocessor evidently tolerated / expanded this differently.

Secondary errors (same root family)

  • chraidata.c:183/202/249/640: macro 'SWITCH' requires 49 arguments, but only 20/14/26/26 given. The SWITCH macro (bondaicommands.h, ~line 776) has 49 fixed parameters (1 VAR + 16×3 case slots) and uses IF_VA(NOT(IS_EMPTY(..))) to detect which cases are present. Game code calls it with fewer args, relying on IDO leniency. GCC enforces the exact arg count.
  • aicommands.def:514:270 / 550:21 / 2846:21: "expected expression before ','" / "expected '}' before 'AI_Return'" / "expected '}' before 'AI_IFICouldSeeBond'". These are downstream of the isSubroutine/DEFINED failure (the CALL expansion aborts mid-macro, leaving dangling tokens). Note the reported line numbers (514/550/2846) do not match the raw aicommands.def lines (those are comment/brace lines) — they are positions in the expanded macro text, not the source file. Don't chase them as source lines.

Key facts / ground truth

  • aicommands.def call counts: 255 _AI_CMD(, 119 _AI_CMD_POLYMORPH(, 2 _AI_DEBUG(.
  • _AI_CMD_POLYMORPH definitions across the tree:
    • aicommands.def:97 — 4-arg (PRE, NAME, CMD_PARAMS_POST, POLY_PARAMS) but inside a #if 0 block (lines 88–100) → inactive.
    • bondconstants.h:721 — 5-arg (CMD, A, P, Q, D) under #ifdef __sgi (not us).
    • bondconstants.h:726 — variadic (...) under #else (this is the one active for GCC when building the AI_CMD enum at bondconstants.h:731).
    • chrai.c:170 — 5-arg (CMD, A, P, Q, D).
    • chrai.c:915 — 5-arg (C, N, P1, P2, D).
  • aicommands.def:85–87: #ifndef __GNUC__ #include <CPPLib.h> #endif → for GCC CPPLib.h is not included from aicommands.def itself; it comes in via bondconstants.h:17.
  • aicommands.def:11888: #undef _AI_CMD_POLYMORPH (late in the file).
  • aicommands2.h is pre-generated (header says "Do not edit … generated by cmdbuilder from src/aicommands.def"). It is included by bondaicommands.h:864 and deb_loadallmodels.c:8. It is not the failing path.
  • isSubroutine is the only macro that feeds a macro call (SETUPSUBROUTINES(ID)) into DEFINED(). That is the unique trigger for the ## pasting error.
  1. Fix the DEFINED(SETUPSUBROUTINES(ID)) pasting — the single root cause of the CALL/isSubroutine errors. Preferred: a port/ shim that redefines DEFINED (or _DEFINED) after CPPLib.h is included, special-casing the SETUPSUBROUTINES(ID) argument so it does not ##-paste a multi-token macro-call. Sketch:
    /* port/shim/... after <CPPLib.h> */
    #undef DEFINED
    #define DEFINED(x) _PORT_DEFINED(x)
    #define _PORT_DEFINED(x) /* if x is SETUPSUBROUTINES(ID) -> PROBE(~), else fall back */
    
    The tricky part: detecting "x is exactly SETUPSUBROUTINES(ID)" in the preprocessor. Alternative: redefine isSubroutine in the shim to compute the SETUPSUBROUTINES presence without DEFINED() (e.g. via a per-setup #define HAS_SETUPSUBROUTINES 0/1 flag set before the include). Investigate which is less invasive before committing.
    • Constraint: bondaicommands.h and CPPLib.h are game files — do not edit them. All fixes go in port/ (shim headers found first via include path) or, if truly unavoidable, a -DPORT-gated src/ change with a PC port: comment.
  2. Fix the SWITCH 49-arg requirement — make a shim SWITCH variadic (or pad the call sites). Verify the IF_VA/IS_EMPTY case-detection still works under GCC once variadic.
  3. Re-verify the aicommands.def:514/550/2846 errors disappear once (1) and (2) are in — they are likely downstream of the CALL expansion abort, not independent bugs.
  4. Rebuild and confirm chraidata.c compiles → full ge007 links.
  5. Record the resolution in docs/PCPortResearch.md §11 D7 (replace the "OPEN" / "TBD" text) and delete this handoff file.

Do NOT

  • Do not edit tools/, Makefile, rsp/, ld/ (N64 build, non-negotiable).
  • Do not edit bondaicommands.h, bondconstants.h, CPPLib.h, aicommands.def, aicommands2.h unless a -DPORT-gated, layout/semantics- preserving change is truly unavoidable (and then flag it PC port:).
  • Do not chase the aicommands.def:514/550/2846 line numbers as source lines (they are expanded-macro positions).