Commit graph

366 commits

Author SHA1 Message Date
1dec6fa00f Improve terse docstrings on Vector, Font, Texture, GridPoint, GridPointState
Replace placeholder docstrings ("SFML Vector Object", "SFML Font Object",
etc.) with comprehensive constructor signatures, argument descriptions,
and property listings matching the documentation standard used by other
types like Color and Frame.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:18:33 -04:00
71ab1dcf2e Add API consistency audit document for 1.0 freeze preparation
Comprehensive catalog of the full Python API surface area:
- 44 exported types, 14 internal types, 10 enums
- 13 module functions, 7 module properties, 5 singletons
- 15 findings across naming, functionality, deprecations, docs

Key findings: camelCase module functions (#304), deprecated
sprite_number (#305), legacy enum string comparisons (#306),
Color missing __eq__ (#307), redundant Grid.position (#308).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 21:18:25 -04:00
cce17fc1ca WIP: update submodule refs for cpython, imgui-sfml, and libtcod-headless
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 01:15:49 -04:00
109bc21d90 Grid/GridView API unification: mcrfpy.Grid now returns GridView, closes #252
mcrfpy.Grid() now creates a GridView that internally owns a GridData (UIGrid).
The old UIGrid type is renamed to _GridData (internal). Attribute access on Grid
delegates to the underlying UIGrid via tp_getattro/tp_setattro, so all existing
Grid properties (grid_w, grid_h, entities, cells, layers, etc.) work transparently.

Key changes:
- GridView init has two modes: factory (Grid(grid_size=...)) and explicit view
  (Grid(grid=existing_grid, ...)) for future multi-view support
- Entity.grid getter returns GridView wrapper via owning_view back-reference
- Entity.grid setter accepts GridView objects
- GridLayer set_grid handles GridView (extracts underlying UIGrid)
- UIDrawable::removeFromParent handles UIGRIDVIEW type correctly
- UIFrame children init accepts GridView objects
- Animation system supports GridView (center, zoom, shader.* properties)
- PythonObjectCache registration preserves subclass identity
- All 263 tests pass (100%)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 04:34:11 -04:00
a61f05229f Per-entity FOV cache for TARGET trigger optimization, closes #303
Add tiered optimization to grid.step() TARGET trigger evaluation:
- Tier 1: O(1) target_label check (already existed)
- Tier 2: O(bucket) spatial hash pre-filter (already existed)
- Tier 3: O(radius^2) bounded FOV via TCOD radius (verified TCOD bounds iteration)
- Tier 4: Per-entity FOV result cache - stores visibility bitmap per entity,
  skips FOV recomputation when entity hasn't moved and map transparency unchanged

Key changes:
- GridData: Add transparency_generation counter, bumped on syncTCODMap/Cell
- UIEntity: Add TargetFOVCache struct with visibility bitmap and validation
- UIGrid::py_step: Restructure TARGET check to collect matching targets first,
  then check/populate per-entity cache before testing visibility
- Entity.find_path(): New convenience method delegating to Grid.find_path
- Grid.find_path/get_dijkstra_map: Add collide parameter for entity-aware
  pathfinding (marks labeled entity cells as non-walkable during computation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 01:34:45 -04:00
c1a9523ac2 Add collision label support for pathfinding (closes #302)
Add `collide` kwarg to Grid.find_path() and Grid.get_dijkstra_map() that
treats entities bearing a given label as impassable obstacles via
mark-and-restore on the TCOD walkability map. Dijkstra cache key now
includes collide label for separate caching. Add Entity.find_path()
convenience method that delegates to the grid.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 01:34:19 -04:00
6a0040d630 Add regression tests for Frame.children mutation and parent=None removal
frame_children_mutation_test: validates remove(), property mutation via
stored refs, fill_color persistence, iteration mutation, pop(), and
while-loop clearing — with visual screenshot verification.

parent_none_removal_test: validates that setting .parent = None removes
children from Frame.children and scene.children, Entity.grid = None
removal, and Grid overlay children removal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 11:27:47 -04:00
b1902a3d8b Add edge-type Wang path overlay as 3rd layer in tiled demo
Generates a network of bezier-curved dirt paths connecting POIs placed
via grid-distributed random selection. Uses minimum spanning tree with
extra fork edges, noise-offset control points for organic curves, and
the "pathways" edge-type Wang set for directional path tiles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 11:27:33 -04:00
916553db26 Update libtcod-headless submodule to include pathfinding additions
Advances to 83b3e6ce which adds Dijkstra distance maps, multi-root
Dijkstra, A* heuristic functions, and pathfinding demo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 11:27:22 -04:00
a35352df4e Phase 4.3: Grid auto-creates GridView with rendering property sync
Grid.__init__() now auto-creates a GridView that shares the Grid's
data via aliasing shared_ptr. This enables the Grid/GridView split:

- PyUIGridObject gains a `view` member (shared_ptr<UIGridView>)
- Grid.view property exposes the auto-created GridView (read-only)
- Rendering property setters (center_x/y, zoom, camera_rotation, x, y,
  w, h) sync changes to the view automatically
- Grid still works as UIDrawable in scenes (no substitution) — backward
  compatible with all existing code and subclasses
- GridView.grid returns the original Grid with identity preservation
- Explicit GridViews (created by user) are independent of Grid's own
  rendering properties

Addresses #252. All 260 tests pass, no breaking changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 11:24:47 -04:00
86f8e596b0 Fix GridView.grid property and add sanitizer stress test
- Implement GridView.grid getter: reconstruct shared_ptr<UIGrid> from
  aliasing grid_data pointer, use PythonObjectCache for identity
  preservation (view.grid is grid == True)
- Add sanitizer stress test exercising entity lifecycle, behavior
  stepping, GridView lifecycle, FOV dedup, and spatial hash churn
- Add GridView.grid identity test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 18:19:33 -04:00
4b13e5f5db Phase 4.2: Add GridView UIDrawable type (addresses #252)
GridView is a new UIDrawable that renders a GridData object independently.
Multiple GridViews can reference the same Grid for split-screen, minimap,
or different camera/zoom perspectives on shared grid state.

- New files: UIGridView.h/cpp with full rendering pipeline (copied from
  UIGrid::render, adapted to use grid_data pointer)
- Add UIGRIDVIEW to PyObjectsEnum
- Add UIGRIDVIEW cases to all switch(derived_type()) sites:
  Animation.cpp, UICollection.cpp, UIDrawable.cpp, McRFPy_API.cpp,
  ImGuiSceneExplorer.cpp
- Python type: mcrfpy.GridView(grid=, pos=, size=, zoom=, fill_color=)
- Properties: grid, center, zoom, fill_color, texture
- Register type with metaclass for callback support

All 258 existing tests pass. New GridView test suite added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 08:41:44 -04:00
13d5512a41 Phase 4.1: Extract GridData base class from UIGrid (#252, #270, #271, #277)
Extract all grid data members and methods into GridData base class.
UIGrid now inherits from both UIDrawable (rendering) and GridData (state).

- GridData holds: grid dimensions, cell storage (flat/chunked), entities,
  spatial hash, TCOD map, FOV state, Dijkstra caches, layers, cell
  callbacks, children collection
- GridData provides: at(), syncTCODMap/Cell(), computeFOV(), isInFOV(),
  layer management (add/remove/sort/getByName), initStorage()
- UIGrid retains: texture, box, sprites, renderTexture, camera (center,
  zoom, rotation), fill_color, perspective, cell hover/click dispatch,
  all Python API static methods, render()

Fix dangling parent_grid pointers: change UIGrid* to GridData* in
GridLayer, UIGridPoint, GridChunk, ChunkManager (closes #270, closes
#271, closes #277). All 258 tests pass unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 07:45:12 -04:00
700c21ce96 Phase 3: Behavior system with grid.step() turn manager
- Add EntityBehavior struct with 11 behavior types: IDLE, CUSTOM,
  NOISE4/8, PATH, WAYPOINT, PATROL, LOOP, SLEEP, SEEK, FLEE.
  Each returns BehaviorOutput (MOVED/DONE/BLOCKED/NO_ACTION) without
  modifying entity position directly (closes #300)
- Add grid.step(n=1, turn_order=None) turn manager: groups entities
  by turn_order, executes behaviors, fires triggers (TARGET/DONE/BLOCKED),
  updates cell_position and spatial hash. Snapshot-based iteration for
  callback safety (closes #301)
- Entity properties: behavior_type (read-only), turn_order, move_speed,
  target_label, sight_radius. Method: set_behavior(type, waypoints,
  turns, path)
- Update ColorLayer::updatePerspective to use cell_position

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 22:14:02 -04:00
2f1e472245 Phase 2: Entity data model extensions for behavior system
- Add Behavior enum (IDLE..FLEE, 11 values) and Trigger enum (DONE,
  BLOCKED, TARGET) as runtime IntEnum classes (closes #297, closes #298)
- Add entity label system: labels property (frozenset), add_label(),
  remove_label(), has_label(), constructor kwarg (closes #296)
- Add cell_pos integer logical position decoupled from float draw_pos;
  grid_pos now aliases cell_pos; SpatialHash::updateCell() for cell-based
  bucket management; FOV/visibility uses cell_position (closes #295)
- Add step callback and default_behavior properties to Entity for
  grid.step() turn management (closes #299)
- Update updateVisibility, visible_entities, ColorLayer::updatePerspective
  to use cell_position instead of float position

BREAKING: grid_pos no longer derives from float x/y position. Use
cell_pos/grid_pos for logical position, draw_pos for render position.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 22:05:06 -04:00
94f5f5a3fd Phase 1: Safety & performance foundation for Grid/Entity overhaul
- Fix Entity3D self-reference cycle: replace raw `self` pointer with
  `pyobject` strong-ref pattern matching UIEntity (closes #266)
- TileLayer inherits Grid texture when none set, in all three attachment
  paths: constructor, add_layer(), and .grid property (closes #254)
- Add SpatialHash::queryCell() for O(1) entity-at-cell lookup; fix
  missing spatial_hash.insert() in Entity.__init__ grid= kwarg path;
  use queryCell in GridPoint.entities (closes #253)
- Add FOV dirty flag and parameter cache to skip redundant computeFOV
  calls when map unchanged and params match (closes #292)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:48:24 -04:00
836a0584df Preserve Python subclass identity for entities in grids (reopens #266)
The Phase 3 fix for #266 removed UIEntity::self which prevented
tp_dealloc from ever running. However, this also allowed Python
subclass wrappers (GameEntity, ZoneExit, etc.) to be GC'd while
the C++ entity lived on in a grid. Later access via grid.entities
returned a base Entity wrapper, losing all subclass methods.

Fix: Add UIEntity::pyobject field that holds a strong reference to
the Python wrapper. Set in init(), cleared when the entity leaves
a grid (die(), set_grid(None), collection removal). This keeps
subclass identity alive while in a grid, but allows proper GC when
the entity is removed. Added releasePyIdentity() helper called at
all grid exit points.

Regression test exercises Liber Noster patterns: subclass hierarchy,
isinstance() checks, combat mixins, tooltip/send methods, GC
survival, die(), pop(), remove(), and stress test with 20 entities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:24:26 -04:00
34c84ce50a Fix UniformCollection owner validity check (closes #272)
UniformCollection accessor methods checked the raw collection pointer
but never checked if the owning object was still alive. Changed owner
field from weak_ptr<UIDrawable> to weak_ptr<void> (type-erased) so
both UIDrawable and UIEntity owners can be tracked. Set owner in both
get_uniforms() paths. All accessors now check owner.lock() before
dereferencing the raw collection pointer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 17:07:14 -04:00
394e79ce88 Fix PythonObjectCache race and document die() iteration (closes #269, closes #273)
Add mutex lock to PythonObjectCache::lookup() - cache.find() was
unprotected against concurrent modification. Document that entity.die()
must not be called during iteration over grid.entities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:33:05 -05:00
115e16f4f2 Convert raw pointers to coordinate-based access (closes #264, closes #265)
GridPoint and GridPointState Python objects now store (grid, x, y)
coordinates instead of raw C++ pointers. Data addresses are computed
on each property access, preventing dangling pointers after vector
resizes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:30:32 -05:00
a12e035a71 Remove entity self-reference cycle
UIEntity::init() stored self->data->self = (PyObject*)self with
Py_INCREF(self), creating a reference cycle that prevented entities
from ever being freed. The matching Py_DECREF never existed.

Fix: Remove the `self` field from UIEntity entirely. Replace all
read sites (iter next, getitem, get_perspective, entities_in_radius)
with PythonObjectCache lookups using serial_number, which uses weak
references and doesn't prevent garbage collection.

Also adds tp_dealloc to PyUIEntityType to properly clean up the
shared_ptr and weak references when the Python wrapper is freed.

Closes #266, closes #275

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:22:58 -05:00
71eb01c950 Replace PyObject_GetAttrString with direct type references
Replace ~230 occurrences of PyObject_GetAttrString(McRFPy_API::mcrf_module, "TypeName")
with direct &mcrfpydef::PyXxxType references across 32 source files.

Each PyObject_GetAttrString call returns a new reference. When used inline
in PyObject_IsInstance(), that reference was immediately leaked. When used
for tp_alloc, the reference required careful Py_DECREF management that was
often missing on error paths.

Direct type references are compile-time constants that never need reference
counting, eliminating ~230 potential leak sites and removing ~100 lines of
Py_DECREF/Py_XDECREF cleanup code.

Also adds extractDrawable() helper in UICollection.cpp to replace repeated
8-way type-check-and-extract chains with a single function call.

Closes #267, closes #268

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:18:42 -05:00
348826a0f5 Fix gridstate heap overflows and spatial hash cleanup
Add ensureGridstate() helper that unconditionally checks gridstate size
against current grid dimensions and resizes if mismatched. Replace all
lazy-init guards (size == 0) with ensureGridstate() calls.

Previously, gridstate was only initialized when empty. When an entity
moved to a differently-sized grid, gridstate kept the old size, causing
heap buffer overflows when updateVisibility() or at() iterated using the
new grid's dimensions.

Also adds spatial_hash.remove() calls in set_grid() before removing
entities from old grids, and replaces PyObject_GetAttrString type lookup
with direct &mcrfpydef::PyUIGridType reference.

Closes #258, closes #259, closes #260, closes #261, closes #262,
closes #263, closes #274, closes #276, closes #278

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:56:16 -05:00
08407e48e1 CI for memory safety - updates 2026-03-07 22:33:01 -05:00
4df3687045 CI memory safety tests 2026-03-07 21:53:19 -05:00
cdae3b3ac9 SDL key scancode fixes (7DRL 2026 hotfix) 2026-03-07 10:08:59 -05:00
120b0aa2a4 Three things, sorry. SDL composite texture bugfix, sprite offset position, some Grid render efficiencies 2026-03-03 23:17:02 -05:00
456e5e676e Version bump: 0.2.7-prerelease-7drl2026 (d496959) -> 0.2.8-7DRL-2026 2026-02-28 11:55:14 -05:00
d496959f8b Windows fix: path doesn't require mode 2026-02-28 11:53:16 -05:00
a52568cc8d entity animation version demo 2026-02-27 22:12:17 -05:00
29fe135161 animation loop parameter 2026-02-27 22:11:29 -05:00
550201d365 CLAUDE guidance 2026-02-27 22:11:10 -05:00
e2d3e56968 Cross-platform persistent save directory (IDBFS on WASM, filesystem on desktop)
Game code uses standard Python file I/O to mcrfpy.save_dir with no platform
branching. On WASM, builtins.open() is monkeypatched so writes to /save/
auto-sync IDBFS on close, making persistence transparent.

API: mcrfpy.save_dir (str), mcrfpy._sync_storage() (auto-called on WASM)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:42:44 -05:00
453ea4a7eb Version bump: 0.2.6-prerelease-7drl2026 (4404d10) -> 0.2.7-prerelease-7drl2026 2026-02-21 07:58:10 -05:00
4404d1082a Update roadmap for 7DRL 2026 and post-jam 1.0 planning
Rewrite ROADMAP.md to reflect current project state:
- Summarize 0.2 series shipped features (3D/voxel, procgen, Tiled/LDtk,
  WASM, animation callbacks, multi-layer grids, doc macros)
- 7DRL 2026 dates (Feb 28 - Mar 8) and remaining prep
- Post-jam priorities: API freeze process, pain point fixes,
  roguelikedev tutorial series, pip/virtualenv integration
- Engine eras model (McRogueFace -> McVectorFace -> McVoxelFace)
- Future directions: McRogueFace Lite (MicroPython/PicoCalc),
  standard library widgets, package management
- Open issue groupings (30 issues across 8 areas)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 23:18:12 -05:00
9176dca055 Add mcrf-init.sh: game project scaffolding without engine recompilation
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>
2026-02-20 23:17:59 -05:00
732897426a Audio fixes: gain() DSP effect, sfxr phase wrap, SDL2 backend compat
- 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>
2026-02-20 23:17:41 -05:00
80e14163f9 Shade sprite module: faction generation, asset scanning, TextureCache
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>
2026-02-20 23:17:24 -05:00
9718153709 Fix callback/timer GC: prevent premature destruction of Python callbacks
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>
2026-02-19 20:53:50 -05:00
97dbec9106 Add SoundBuffer type: procedural audio, sfxr synthesis, DSP effects
New SoundBuffer Python type enables procedural audio generation:
- Tone synthesis (sine, square, saw, triangle, noise) with ADSR envelopes
- sfxr retro sound effect engine (7 presets, 24 params, mutation, seeding)
- DSP effects chain: pitch_shift, low/high pass, echo, reverb,
  distortion, bit_crush, normalize, reverse, slice
- Composition: concat (with crossfade overlap) and mix
- Sound() now accepts SoundBuffer or filename string
- Sound gains pitch property and play_varied() method
- Platform stubs for HeadlessTypes and SDL2Types (loadFromSamples, pitch)
- Interactive demo: sfxr clone UI + Animalese speech synthesizer
- 62 unit tests across 6 test files (all passing)

Refs #251

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 18:58:11 -05:00
bb72040396 Migrate static PyTypeObject to inline, delete PyTypeCache workarounds
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>
2026-02-16 20:58:09 -05:00
6fdf7279ce Shade (merchant-shade.itch.io) entity animation tests 2026-02-16 20:19:39 -05:00
2681cbd957 Crypt of Sokoban remaster continued 2026-02-16 18:39:38 -05:00
686e4fc1b2 Replace forward BFS solver with reverse-pull puzzle generation
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>
2026-02-15 08:36:43 -05:00
4c809bdd0f Fix Sokoban puzzle: prevent treasure/button overlap and model obstacles in solver
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>
2026-02-15 02:36:46 -05:00
99f439930d Crypt of Sokoban remaster: BSP, FOV, enemy AI, solvability checker
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>
2026-02-14 20:34:14 -05:00
7855a7ad80 Version bump: 0.2.5-prerelease-7drl2026 (50eba33) -> 0.2.6-prerelease-7drl2026 2026-02-14 11:13:57 -05:00
50eba3314b more gitignore 2026-02-14 11:04:28 -05:00
945cce3f88 crypt of sokoban layer API change updates 2026-02-14 11:04:19 -05:00
726a9cf09d Mobile-"ish" emscripten support
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.
2026-02-09 08:40:34 -05:00