New workflow for game developers: run mcrf-init to create a project
directory with symlinks to a pre-built engine, then just write Python
scripts and assets. Games package for distribution (Linux/Windows/WASM)
without ever rebuilding the engine.
mcrf-init.sh creates:
- build/ with symlinked binary and libs, game content in assets/ + scripts/
- build-windows/ (if engine has a Windows build)
- Makefile with run, wasm, dist-linux, dist-windows, dist-wasm targets
- Starter game.py, .gitignore, pyrightconfig.json, VERSION file
CMakeLists.txt: WASM preload paths (assets, scripts) are now
configurable via MCRF_ASSETS_DIR / MCRF_SCRIPTS_DIR cache variables,
so game project Makefiles can point WASM builds at their own content
without modifying the engine.
Also adds pyrightconfig.json for the engine repo itself (IDE support
via stubs/).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SoundBuffer.gain(factor): new DSP method for amplitude scaling before
mixing (0.5 = half volume, 2.0 = double, clamped to int16 range)
- Fix sfxr square/saw waveform artifacts: phase now wraps at period
boundary instead of growing unbounded; noise buffer refreshes per period
- Fix PySound construction from SoundBuffer on SDL2 backend: use
loadFromSamples() directly instead of copy-assign (deleted on SDL2)
- Add Image::create(w, h, pixels) overload to HeadlessTypes and
SDL2Types for pixel data initialization
- Waveform test suite (62 lines)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extends the shade_sprite module (for merchant-shade.itch.io character
sprite sheets) with procedural faction generation and asset management:
- FactionGenerator: seed-based faction recipes with Biome, Element,
Aesthetic, and RoleType enums for thematic variety
- AssetLibrary: filesystem scanner that discovers and categorizes
layer PNGs by type (skins, clothes, hair, etc.)
- TextureCache: avoids redundant disk I/O when building many variants
- CharacterAssembler: HSL shift documentation, method improvements
- Demo expanded to 6 interactive scenes (animation viewer, HSL recolor,
character gallery, faction generator, layer compositing, equipment)
- EVALUATION.md: 7DRL readiness assessment of the full module
- 329-line faction generation test suite
Assets themselves are not included -- sprite sheets are external
dependencies, some under commercial license.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
closes#251
Two related bugs where Python garbage collection destroyed callbacks
that were still needed by live C++ objects:
1. **Drawable callbacks (all 8 types)**: tp_dealloc unconditionally called
click_unregister() etc., destroying callbacks even when the C++ object
was still alive in a parent's children vector. Fixed by guarding with
shared_ptr::use_count() <= 1 — only unregister when the Python wrapper
is the last owner.
2. **Timer GC prevention**: Active timers now hold a Py_INCREF'd reference
to their Python wrapper (Timer::py_wrapper), preventing GC while the
timer is registered in the engine. Released on stop(), one-shot fire,
or destruction. mcrfpy.Timer("name", cb, 100) now works without storing
the return value.
Also includes audio synth demo UI fixes: button click handling (don't set
on_click on Caption children), single-column slider layout, improved
Animalese contrast.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 27 PyTypeObject declarations in namespace mcrfpydef headers changed
from `static` to `inline` (C++17), ensuring a single global instance
across translation units. This fixes the root cause of stale-type-pointer
segfaults where only the McRFPy_API.cpp copy was PyType_Ready'd.
Replaced ~20 PyTypeCache call sites and 2 PyRAII::PyTypeRef lookups with
direct &mcrfpydef::Type references. Deleted PyTypeCache.h/.cpp,
PyObjectUtils.h, and PyRAII.h (all were workarounds for the static bug).
228/228 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The BFS solver couldn't account for obstacles blocking push paths -
knowing the button is reachable doesn't mean the player can get to
the correct side of the boulder. Reverse-pull guarantees solvability
by construction: start with boulder on button, simulate valid
un-pushes to move it away. Each un-push verifies both the new boulder
cell and the player's required push position are walkable.
Also fixes chest clumping: level 2 previously crammed 3 treasures +
boulder + button into a single room. Redesigned all level plans to
spread treasures across rooms (max 1 per room). Updated lv_planner
for procedural levels 9+ with the same constraint.
Level plans no longer specify "boulder" - it's auto-generated from
the button position with min_pulls scaling by depth (2-8).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three bugs that could produce unsolvable puzzles:
1. Feature placement fallback used leaf_center without duplicate checking,
allowing treasures to spawn on the same cell as buttons in dense rooms
(level 2 puts 8 features in one room). Fixed with exhaustive cell scan
fallback.
2. Solvability checker ignored treasure entities entirely. Boulders cannot
be pushed through treasures (TreasureEntity.bump returns False for
non-player entities), so the solver now models them as boulder-blocking
obstacles while allowing player movement through them.
3. Added explicit button-blocked-by-obstacle check before running the
full BFS solver, catching the most common failure mode early.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces jam-quality code with production engine features (addresses #248):
- cos_level.py: Replace custom BinaryRoomNode/RoomGraph with mcrfpy.BSP
for room generation, using adjacency graph for corridor placement
- cos_solver.py: New Sokoban BFS solvability checker; levels retry up
to 10 times if unsolvable
- game.py: Add ColorLayer fog of war with room-reveal mechanic
(visible/discovered/unknown states), compute_fov per player move
- cos_entities.py: Enemy AI state machine (idle/aggressive/fleeing)
with A* pathfinding, fix duplicate direction bug on line 428/444
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full screen "wasm-game" for viewport compatibility between desktop and
web interfaces. Viewport modes ("fit", "center", and "stretch") should
now work the same way under WASM/SDL and SFML. This should also enable
android or web-for-mobile aspect ratios to be supported more easily.
Replace no-op audio stubs in SDL2Types.h with real SDL2_mixer-backed
implementations of SoundBuffer, Sound, and Music. This enables audio
playback in the browser with zero changes to Python bindings.
- Add -sUSE_SDL_MIXER=2 to Emscripten compile/link flags (CMakeLists.txt)
- Initialize Mix_OpenAudio in SDL2Renderer::init(), Mix_CloseAudio in shutdown()
- SoundBuffer: Mix_LoadWAV/Mix_LoadWAV_RW with duration computation
- Sound: channel-based playback with Mix_ChannelFinished tracking
- Music: global channel streaming via Mix_LoadMUS/Mix_PlayMusic
- Volume conversion: SFML 0-100 scale to SDL_mixer 0-128 scale
Known limitations on web: Music.duration and Music.position getters
return 0 (SDL_mixer 2.0.2 lacks Mix_MusicDuration/Mix_GetMusicPosition).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaced the NotImplementedError stub with a full animation
implementation. Entity3D now supports animating: x, y, z,
world_x, world_y, world_z, rotation, rot_y, scale, scale_x,
scale_y, scale_z, sprite_index, visible.
Added Entity3D as a third target type in the Animation system
(alongside UIDrawable and UIEntity), with startEntity3D(),
applyValue(Entity3D*), and proper callback support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EntityCollection3D now has API parity with UIEntityCollection:
- pop(index=-1): Remove and return entity at index
- find(name): Search by entity name, return Entity3D or None
- extend(iterable): Append multiple Entity3D objects
Also adds `name` property to Entity3D for use with find().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
screen_to_world() previously only intersected the Y=0 plane.
Now accepts an optional y_plane parameter (default 0.0) for
intersecting arbitrary horizontal planes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously enableRenderTexture() silently failed. Now emits a
stderr warning with the requested dimensions, consistent with
the engine's logging pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The root cause was PyViewport3DType being declared `static` in
Viewport3D.h, creating per-translation-unit copies. Entity3D.cpp's
copy was never passed through PyType_Ready, causing segfaults when
tp_alloc was called.
Changed `static` to `inline` (matching PyEntity3DType and
PyModel3DType patterns), and implemented get_viewport using the
standard type->tp_alloc pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>