Files
Cesarus85--ringfall-mjolnir-vr/docs/assertions.md
T

5.9 KiB

Assertions, warnings, and halts: shared API and January matching

The owner is source/cseries/cseries.h. All assertions express a condition that must be true. A false condition reports the diagnostic with display_assert(..., TRUE) and then calls system_exit(-1). Evaluate diagnostic expressions only on the failure path.

Warnings likewise report only when their condition is false, but use display_assert(..., FALSE) and continue. Halts are unconditional: they report with TRUE and call the existing halt_and_catch_fire() owner. They do not take an assertion condition.

Intent Current supported API Diagnostic
Plain assertion assert(expression) Stringified expression
Diagnostic assertion dassert(expression, diagnostic) Supplied string expression
Formatted assertion vassert(expression, csprintf(temporary, format, ...)) Formatted string, produced only on failure
Preserve original source location match_assert(file, line, expression) Stringified expression
Preserve location and diagnostic match_dassert(file, line, expression, diagnostic) Supplied string expression
Preserve location and formatted diagnostic match_vassert(file, line, expression, csprintf(temporary, format, ...)) Formatted string
Plain warning warn(expression) Stringified expression; nonfatal
Diagnostic warning dwarn(expression, diagnostic) Supplied string; nonfatal
Formatted warning vwarn(expression, csprintf(temporary, format, ...)) Failure-only formatting; nonfatal
Plain halt halt() No message; unconditional fatal report
Diagnostic halt dhalt(diagnostic) Supplied string; unconditional
Formatted halt vhalt(csprintf(temporary, format, ...)) Formatted string; unconditional

Warnings and halts also have match_warn, match_dwarn, match_vwarn, match_halt, match_dhalt, and match_vhalt counterparts. Their first two arguments are the original file and line, followed by the ordinary API's arguments.

vassert/match_vassert already accepted a message expression before this cleanup. The dassert/dwarn/dhalt names and matching counterparts are statement-safe diagnostic convenience wrappers for the existing behavior, not a claim that these exact wrappers were in Bungie's January source. Existing callers remain compatible.

The pinned XDK 3911 compiler is VC7 13.00.9254.1. Its preprocessor rejects #define vassert(expression, format, ...) with C2010, and does not implement __VA_ARGS__. Variadic functions such as csprintf are supported. Do not change compiler versions, add a function-call wrapper, or force inline code merely to obtain the newer comma-separated macro syntax.

The supplied CE reference files themselves use two-argument vassert(expression, message) calls: random_math.c's random-seed lock checks, server line 1248, and client line 521. These calls corroborate the older API, but the attachments do not include its defining cseries.h. See the source reconstruction map.

Units example: assert the invariant, not the error condition

The invariant is that an inventory weapon being dropped is not connected to the map:

match_vassert(
    "c:\\halo\\SOURCE\\units\\units.c",
    8505,
    !TEST_FLAG(weapon->object.flags, _object_connected_to_map_bit),
    csprintf(
        temporary,
        "a %s tried to drop a %s which was connected to the map.",
        tag_get_name(unit->definition_index),
        tag_get_name(weapon->definition_index)));

Omitting ! reverses the behavior: it would assert for a disconnected weapon and accept the connected weapon. Keep the two tag_get_name calls and the formatting inside the assertion message expression, so successful execution does not evaluate them.

The matching form retains January's original filename and line number. In a NON_MATCHING build, MATCH_FILE/MATCH_LINE deliberately use the actual current source location instead. For new code with no original location to preserve, use the ordinary assert/dassert/vassert form.

Limits and adoption

  • Use braces around surrounding if/else branches. Some inherited macros expand to an if statement; an unbraced outer else can bind incorrectly.
  • Keep expressions free of unnecessary side effects. The condition is evaluated once, and the diagnostic only on failure, but argument evaluation order inside a formatted call remains the C compiler's responsibility.
  • temporary is the existing shared 256-byte buffer and csprintf uses unbounded vsprintf. Preserve authenticated legacy call sites; this is not a thread-safe or size-safe general logging facility. Do not pass arbitrary unbounded input or preformat into this buffer on the success path.
  • Do not add NDEBUG-based removal: the current project macros do not have that contract. Both debug and NDEBUG fixture builds are tested.
  • Migrate one coherent owner at a time and run its strict gate plus consumer regression checks. Do not bulk-rewrite active lanes or lose original diagnostic text, argument types, failure polarity, or source locations.

The executable macro tests are tools/test_cseries_assertions.py, using C fixtures compiled by the actual VC7 toolchain. They check success/failure, lazy formatting, single evaluation, exact matching/nonmatching locations, fatal flag and system_exit(-1) or halt_and_catch_fire() dispatch, nonfatal warning continuation, and connected-weapon polarity. They use bounded diagnostic API doubles; they do not validate the termination implementation, vsprintf buffer safety, or the game's complete error-reporting subsystem.

See also common constants and the Units cleanup ledger.