Commit graph

12 commits

Author SHA1 Message Date
77dd0fc496 fix(wasm): serve and ship the clean game build, not the REPL-shell hybrid
make serve and the release WASM.zip both sourced build-emscripten (make
wasm), which bundles the full game (game.py) but defaults to shell.html --
the playground shell with a CodeMirror code panel and a default snippet. So
the 'full game for web' (and the shipped McRogueFace-<ver>-WASM.zip) ran the
game behind an interactive Python console it should not have.

Keep make wasm as the game+REPL dev/debug build (now served by the new
serve-dev target), and point make serve and the release packaging at
build-wasm-game (make wasm-game: game.py + shell_game.html, no panel), which
was already correct but unused. Clarify the target descriptions so the two
don't silently drift again.

The deployed site /playground/ is unaffected -- it is built from make
playground (scripts_playground + code panel) and remains correct, so #382
snippet-links target a clean playground.

closes #384

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LTh2ZW7bd3XSd9qK86Z2CE
2026-07-15 21:55:19 -04:00
07149c100c build(docs): chain snippet-shots into release-docs (#381)
The doc-site's build_library.py now embeds a preview PNG per snippet and
fails loudly if snippet-shots/ is empty, so the release documentation pass
must regenerate the screenshots before the site build runs.

Addresses #381.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LTh2ZW7bd3XSd9qK86Z2CE
2026-07-15 21:27:52 -04:00
cf9512e128 feat(docs): deterministic snippet screenshots — Phase 1 (STATIC capture)
Generate a byte-stable preview PNG for every docs snippet, as the visual-
regression oracle foundation of #381. A changed image means the snippet's
behaviour changed and wants review.

Pipeline (no C++ change; existing headless build suffices):
- _seed.py: chained as the FIRST --exec so random.seed(42) governs the
  snippet's RNG before it draws (snippets draw at import; seeding after is
  too late). Shared-interpreter seeding covers all 25 random-module snippets.
- _screenshot.py: chained as the LAST --exec; steps setup frames then
  automation.screenshot(). Kept separate from _harness.py so the pass/fail
  suite stays fast and untouched.
- tools/generate_snippet_shots.py: orchestrator; runs seed->snippet->shot,
  writes gitignored snippet-shots/ (images belong in the doc-site repo,
  which pulls them from here). Per-snippet capture params live in a sidecar
  OVERRIDES table, not the mcrf: header (which would fight stamp_snippets
  and can't hold Phase-3 interaction scripts).
- make snippet-shots target; snippet-shots/ gitignored.

The 4 BSP snippets that needed deterministic pixels (060, 102, 224, 243)
now pass seed=42 — libtcod's global RNG is time(0)-seeded and unreachable
from Python, and seed= also documents reproducible generation for readers.

Validated: 272 captured, 10 skipped (noshot opt-outs), 0 failed; all 272
byte-identical across two full runs (~49s).

Addresses #381.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LTh2ZW7bd3XSd9qK86Z2CE
2026-07-15 20:57:04 -04:00
50d6f1e5d2 build(docs): chain the release documentation pipeline into make release-docs
Every piece of this pipeline already existed -- generate_all_docs.sh,
generate_api_manifest.py, api_delta.py (with its --site-dir page resolver and its
--format gitea checklist), and the site's build_reference.py / build_library.py,
which already knew how to read this repo. Nothing chained them. So the site's
generated reference sat 26 engine commits behind, stamped verified_commit b6720f6,
and nothing noticed or could have noticed.

  make docs             regenerate man page, API reference, stubs, manifest
  make stamp-snippets   re-run the docs snippets; stamp what actually happened
  make check-snippets   CI gate: fail on a broken snippet or a stale stamp
  make api-delta        what changed, and which site pages that obligates
  make release-docs     all of it, for cutting a tag

release-docs deliberately does not commit and does not touch the hand-written
pages. It refreshes what is derived, proves the published samples still run,
rebuilds the site's generated half against this engine, and prints the checklist of
curated pages the API change obliges you to revisit. The judgment stays with a
human; only the mechanical parts are mechanical.

Two ref-shaped traps, both hit while building this:

  * BASE_REF and RELEASE_REF are different refs and I had conflated them. The delta
    is measured FROM the previous release; the site's source links are pinned TO the
    tag being cut. One variable could not be both.

  * api_delta now distinguishes "this ref predates the manifest" from "this ref is
    broken". 0.2.8 is older than the manifest infra (54624b3), so it has no baseline
    -- every object would read as "added", which is noise, not a delta. It says so
    and exits clean. An unknown ref is still an error.

stamp_snippets --check compares only the CLAIMS a header makes (status, objects),
never `verified`. That field records the engine a snippet was last confirmed
against -- provenance, not a freshness assertion. Had --check demanded it equal
HEAD, every commit would have invalidated all 130 stamps and the gate would have
cried wolf until people stopped listening.

Running it regenerated all 52 reference pages against the current engine, which
cleared every stale UIGrid / compute_astar / gridstate mention the docs audit found
in the generated tree -- the engine's docstrings had been right for weeks; the site
had simply never been rebuilt.
2026-07-14 18:33:31 -04:00
54624b353d Untrack generated docs; add API manifest infra and pre-commit hook
Rendered/generated documentation no longer lives in git -- it's regenerated
by a pre-commit hook (tools/hooks/pre-commit, installed via `make
install-hooks`) and shipped inside release artifacts (tools/package.sh) instead.
A compact api/manifest.json (signatures + docstring hashes + carried-forward
lifecycle metadata: since/modified per object and member) is the one exception
and stays committed, refreshed by the same hook, so API changes remain
trackable by commit/tag via `git show <ref>:api/manifest.json` and
`tools/api_delta.py REF1 REF2` without needing a full rebuild at old refs.

- tools/generate_api_manifest.py: engine-side introspection, run headless;
  emits api/manifest.json (committed) and docs/generated/api_full.json
  (gitignored, full docstrings -- the docs-site generator's data source).
- tools/api_delta.py: stdlib-only diff CLI (md/json/gitea-checklist formats),
  with --site-dir to cross-reference affected docs-site pages via their mcrf
  frontmatter.
- tools/hooks/pre-commit: incremental build + frozen-docstring gate + doc
  regen + manifest refresh, gated on staged src/ changes.
- .gitignore covers the rendered docs/stubs plus personal working-notes docs
  (plans, audits, research write-ups) that were never McRogueFace-user-facing.
- docs/api-stability.md and docs/threading-model.md removed: their content is
  now the wiki's "API Stability Contract" and "Threading Model" system pages
  (ratified 2026-07-02, migration verified 2026-07-11); the repo continues to
  enforce them via tests/unit/api_surface_snapshot_test.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11 20:57:31 -04:00
03c9b9ff58 Add profiling build target + native profiler workflow; closes #345
The default Release build (-O3 -DNDEBUG) omits frame pointers and ships no
DWARF, so it can't be line-annotated or unwound; build-debug (-O0) profiles
unoptimized code. Add a dedicated profiling variant for C++ hot-path work
(validating #331/#342/#343/#344).

- CMakeLists.txt: MCRF_PROFILE option adds -fno-omit-frame-pointer on top of
  RelWithDebInfo (-O2 -g). Binary not stripped. Post-build lib/assets/scripts
  copy already keys on the mcrogueface target, so build-profile/ self-populates.
- Makefile: `make profile` (-> build-profile/), `make callgrind SCRIPT=...`
  (one-shot Callgrind on a headless benchmark), `make clean-profile` (wired
  into clean-all).
- docs/profiling.md: full Callgrind + perf workflow, incl. the one-time
  `sudo sysctl kernel.perf_event_paranoid=1` perf needs on this box.
- Pointers from the Makefile header and CLAUDE.md build section.

Verified: DWARF sections present, frame-pointer prologues emitted, headless
smoke test passes, Callgrind yields source line + call-count attribution, and
perf --call-graph fp resolves engine symbols.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 18:59:39 -04:00
925699ef0b Add 4 libFuzzer targets for Tier A/B API surface; addresses #312
New targets under tests/fuzz/, wired into Makefile FUZZ_TARGETS, each with a
seed corpus (parser seeds are real fixtures prefixed with a loader selector
byte):

- fuzz_audio_dsp        SoundBuffer factories + 14 DSP effects + concat/mix.
                        Self-contained (CPU sample math, no device).
- fuzz_import_parsers   TileSetFile/TileMapFile/LdtkProject. Loaders take a
                        path, so each iteration writes mutated bytes to a temp
                        file; OSError (IOError) is swallowed as an expected
                        parse-failure outcome.
- fuzz_texture_factory  Texture.from_bytes/composite/hsl_shift byte ingestion.
                        Multiplication-overflow path documented as out of scope
                        (would OOM, not crash cleanly).
- fuzz_shader_bindings  uniforms[] + PropertyBinding/CallableBinding lifetime,
                        target Drawable destroyed mid-flight (#270/#271/#277
                        pattern). Degrades to pure binding-lifetime fuzzing if
                        shaders are unavailable.

All four signature-validated against the live mcrfpy API before running.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnywUddaFRhkxo5kijxJnv
2026-06-21 16:44:47 -04:00
90a2945a9f Add native libFuzzer fuzz harness for Python API, addresses #283
Pivots away from atheris (which lacks Python 3.14 support) to a single
libFuzzer-linked executable that embeds CPython, registers mcrfpy, and
dispatches each iteration to a Python fuzz_one_input(data: bytes) function
loaded from tests/fuzz/fuzz_<target>.py by MCRF_FUZZ_TARGET env var.

libFuzzer instruments the C++ engine code where all #258-#278 bugs live;
Python drives the fuzzing logic via an in-house ByteStream replacement
for atheris.FuzzedDataProvider. Python-level exceptions are caught; only
ASan/UBSan signal real bugs.

CMake
- MCRF_FUZZER=ON builds mcrfpy_fuzz from all src/*.cpp except main.cpp
  plus tests/fuzz/fuzz_common.cpp, linked with -fsanitize=fuzzer,address,
  undefined. Asset+lib post-build copy added so the embedded interpreter
  finds its stdlib and default_font/default_texture load.

Makefile
- fuzz-build builds only mcrfpy_fuzz (fast iterate)
- fuzz loops over six targets setting MCRF_FUZZ_TARGET for each
- fuzz-long TARGET=x SECONDS=n for deep manual runs
- fuzz-repro TARGET=x CRASH=path for crash reproduction
- Shared ASAN_OPTIONS / PYTHONHOME env via FUZZ_ENV define

tests/fuzz
- fuzz_common.cpp: LLVMFuzzerInitialize bootstraps Python, imports target,
  resolves fuzz_one_input. LLVMFuzzerTestOneInput wraps bytes as PyBytes,
  calls target, swallows Python errors.
- fuzz_common.py: ByteStream byte consumer + safe_reset() + EXPECTED_EXCEPTIONS
- Six target stubs (grid_entity, property_types, anim_timer_scene,
  maps_procgen, fov, pathfinding_behavior) to be fleshed out in follow-up
- README with build/run/triage instructions

Verified end-to-end: make fuzz-build produces build-fuzz/mcrfpy_fuzz,
make fuzz FUZZ_SECONDS=3 ran all six targets (~2400-9800 exec/s each,
667-1883 coverage edges), make fuzz-repro loaded and replayed a corpus
input cleanly. No crashes from the stubs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:05:04 -04:00
136d2a2a25 Add build plumbing for libFuzzer+ASan fuzz build, addresses #283
- CMakeLists MCRF_FUZZER option (clang-only, -fsanitize=fuzzer-no-link)
- Makefile fuzz-build/fuzz/fuzz-long/fuzz-repro/clean-fuzz targets
- CommandLineParser -- passthrough after --exec for forwarding libFuzzer argv
- McRFPy_API: forward script_args to sys.argv in --exec mode so atheris.Setup()
  sees libFuzzer flags; set sys.argv[0] to the exec script path to match Python
  script-mode conventions
- .gitignore build-fuzz/ and corpora/crashes dirs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:35:44 -04:00
f3ef81cf9c Add ThreadSanitizer build targets and free-threaded Python support, closes #281, closes #280
CMake: Add MCRF_FREE_THREADED_PYTHON option to link python3.14t with
Py_GIL_DISABLED. Extends __lib_debug/ link path for free-threaded builds.

Makefile: Add `make tsan` and `make tsan-test` targets for ThreadSanitizer
builds using free-threaded CPython. Add build-tsan to clean-debug.

The instrumented libtcod build script (tools/build_debug_libs.sh) was
included in the prior commit - it builds libtcod-headless with ASan/TSan
instrumentation for full sanitizer coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 04:08:40 -04:00
5ce5b5779a Add Emscripten debug build targets with DWARF and source maps, closes #238
Adds MCRF_WASM_DEBUG CMake option that enables -g4, -gsource-map, and
--emit-symbol-map for WASM builds. New Makefile targets: wasm-debug,
playground-debug, serve-wasm-debug, serve-playground-debug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 03:31:25 -04:00
188b312af0 Re-enable ASan leak detection, add Massif heap profiling target
#286: Change detect_leaks=0 to detect_leaks=1 in asan-test target.
LSAN suppressions for CPython intentional leaks (interned strings, type
objects, small int cache, etc.) were already in sanitizers/asan.supp.
Now that #266 and #275 are fixed, real McRogueFace leaks will be caught.

#284: Add make massif-test target that runs stress_test_suite.py under
Valgrind Massif for heap profiling. Output goes to build-debug/massif.out,
viewable with ms_print.

Closes #286, closes #284

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 01:08:54 -04:00