Skip to content

Logging (BMFT.Log)

Status: shipped "testing"... actually always-on for error/warn. Unlike everything else in this guide, BMFT.Log isn't behind a feature flag — it's dev/debug infrastructure, not player-facing behavior. Use it instead of raw console.* or the base game's (consolelog:) macro so your mod's log output shares one tag convention, one level/build-mode gating table, and lands in the shared ring buffer the debug console (below) can dump.

js
window.BMFT.Log.warn("[YourMod]", "something degraded, here's why");
window.BMFT.Log.info("[YourMod]", "a state transition happened");
twee
(bmft_log: "[YourMod]", "warn", "something degraded, here's why")
  • tag should be a bracketed component name, matching the base game's own established convention ([XCLStorage], [SaveSystem], ...) — e.g. "[YourMod]" or "[YourMod.Feature]" for something more specific.
  • Levels are debug/info/warn/error, same vocabulary as (consolelog:). error and warn print in every build, including a real release — keep them rare and genuinely actionable. debug/info only print when window.BMFT_BUILD_MODE === "testing" (a stage-mod-for-testing.js build), so routine tracing never reaches a real player's console.
  • Every call is recorded to a capped ring buffer (BMFT.Log.recent(n), MAX_BUFFER entries) regardless of level or build mode — even a debug call that never printed anywhere is still there if you go looking.
  • The rule that matters most: log a breadcrumb at every silent fallback/early-return branch, not just genuine errors. A branch being silent for the player (a feature gracefully degrading, a lookup that didn't find anything) is not the same as it being silent for you — that's exactly the gap that let a real rendering bug ship undetected until a screenshot caught it (2026-08-20, CHANGELOG.md).

The shared debug console

In a testing build (stage-mod-for-testing.js), the sticky note itself renders a small "[bmft debug console]" link in its bottom-right corner — click it ((goto:"bmft debug console") under the hood) to open a hub for checking state and exercising features without editing save data by hand: current day, the log buffer (readable, color-coded by level), and controls to post test sticky notes, fire a toast, advance $day, and clear state. The link itself only renders in a testing build, and the passage is inert even if somehow reached another way (window.BMFT_BUILD_MODE === "testing", same gate) — not just unlinked, but a no-op. There's no other way in: XCL Desktop never exposes DevTools to a player, and the base game's own debug hotkeys aren't confirmed usable for passage navigation either (docs/arch/11-debugging-and-logging.md, xls umbrella repo) — so if you're adding a new debug entry point of your own, route it through this same link rather than assuming a player/tester has another way to reach it. Add your own subsystem's controls to bmft debug console body as it matures, rather than building a one-off debug passage per feature.

Sample data is manual-only: nothing auto-posts on game load. Click the console's own "Post 6 sample sticky notes" link to seed the addon board. An earlier version auto-reset the board on every game load in a testing build; removed (2026-08-20) once that turned out to clobber whatever state was actually being tested on each reload — manual-only is the settled behavior.

"« Back to game" at the top of the console returns to whatever screen you opened it from — same target-resolution logic as the base game's own "go back" passage (pops $menu_stack, falls back to $last_passage_displayed, then $current_activity, then "day"), but ending in (goto:$next) instead of (display:"go back")'s own (cs:). This is a deliberate difference, not a coincidence: (cs:) only repaints the center ?screen hook, by finding an existing one in the current passage's DOM — from a [fullscreen] passage reached via (goto:) (no 3-pane ?screen/?right_screen layout underneath it at all), that lookup always misses, so (display:"go back") alone left both sidebars stale (confirmed by playtest, 2026-08-20). (goto:$next) triggers a full passage re-render instead, which rebuilds the sidebar layout from scratch. Since (goto:"bmft debug console") doesn't push onto $menu_stack itself, this still lands you back on whatever screen was actually showing before you opened the console.

Text and clickable-control sizing both honor the base game's own accessibility settings (Settings > Size Controls > "Font size" / "Button size") rather than using fixed sizes: text scales via calc(<size> * var(--font-size-modifier, 1)), and every tw-link control ((link:) renders as a real <tw-link> element, not <a> — style that tag, not a) scales its padding/tap-target via calc(<size> * var(--button-size-modifier, 1)). If you add your own controls to bmft debug console body, follow the same pattern rather than a fixed px/unscaled em — a fixed size looks fine at 100% and breaks the accessibility slider's whole point for a player relying on it.

Zlib Licensed