The user asked to go back to the pre-commit assets. The original
BMP_{1..4}_*.png tiles have been restored. The generator script
introduced in that commit is also removed.
The original BMP_{1..4}_{N,S,E,W,NE,NW,SE,SW,EN,ES,WN,WS}.png tiles were
very noisy low-quality checkerboard patterns. They are now redrawn as
hand-styled pixel-art bush/hedge tiles:
- 5-color per-theme palette (deep shadow, dark, mid, light, highlight)
- irregular wall/passage boundary with natural erosion
- shaded interior (darker in the center = depth)
- scattered leaf clusters and highlights
- kept 32x32 resolution and engine transparent-gray convention
A new generator tool is added at tools/redraw_walls.py. Run it again
with python3 tools/redraw_walls.py to regenerate the tiles if you want
to iterate on the style or palettes.
Resolves NameError when toggling pause (and any other control path
referencing state_machine.GameState). The module was referenced but
never imported after the recent cheat/controls edits.
Four gameplay parameters are now driven by the selected difficulty,
following the existing 'hard = harder for the player' philosophy:
param easy normal hard
gas_spread_speed 40 50 90 (higher = slower gas)
weapon_refill_multiplier 1.3 1.0 0.6 (less ammo on hard)
max_babies 2 3 5 (more pups on hard)
mate_stop_male / female 120/240 100/200 60/120 (shorter stop on hard)
Implementation:
- engine/config.py: new keys on each DIFFICULTY_OPTIONS entry.
- rats.py: _apply_difficulty() loads them onto the game (with __init__
defaults for safety); normal keeps the previous hardcoded values.
- units/gas.py: Gas.speed reads game.gas_spread_speed (fallback 50).
- engine/unit_manager.py: refill_ammo() scales per-frame refill chances
by game.weapon_refill_multiplier (fallback 1.0).
- units/rat.py: Male.fuck() uses game.mate_stop_male/mate_stop_female
and random.randint(1, game.max_babies) (fallbacks to old values).
All reads use getattr with the previous constant as fallback, so older
configs / saved state keep working.
During gameplay, pressing Ctrl+Return triggers an immediate level clear:
the current level is marked as won (level_clear, or run_complete on the
last DAT level) and the state transitions to VICTORY, showing the
normal 'Level Clear!' dialog so the player can then advance with Return.
Implementation:
- engine/sdl2.py: mainloop detects Ctrl+Return via SDLK_RETURN + KMOD_CTRL
and dispatches a 'cheat_win_level' action (bypassing keybindings).
- engine/controls.py: trigger() now also accepts direct action names that
are registered in the dispatcher (not key-event names), and a new
cheat_win_level handler forwards to the game.
- rats.py: MiceMaze.cheat_win_level() performs the win, guarded so it only
fires while a level is actively being played.
No keybinding files need editing; the cheat is wired through the engine
layer directly.
Rats are now drawn fully (single animation frame) regardless of whether
they are on an open cell, a tunnel entrance, or an internal tunnel
passage. The only case where the rat is not drawn is when its center is
inside a wall (non-empty, non-tunnel cell).
Removed the now-unused _get_tunnel_entrance_direction helper.
The clipping logic that sliced the rat sprite when entering/leaving a
single-entrance tunnel produced a jarring half-rat visual. Removed it
entirely: a rat on a single-entrance tunnel cell is now simply not
drawn (it disappears into the tunnel) instead of being partially
clipped. Internal tunnel passages are unchanged and still render the
rat normally.
After merging new-newnassets, the rat sprite assets are now horizontal
4-frame sprite sheets (e.g. BMP_MALE_UP.png is 80x40 instead of 20x40).
The drawing path for rats inside internal tunnel passages still called
draw_image() without source_rect, so the engine rendered all 4 frames
side by side as a single wide strip (the visual artifact shown in
/tmp/pi-clipboard-...png).
This brings the tunnel-internal rendering in line with the non-tunnel
path, which already used source_rect = (frame * w, 0, w, h).
Tests/test_gray_strip_artifact.py renders lose.png on a white panel
both with and without the near-white normalization, saves the
results to /tmp/test_loss_before.png and /tmp/test_loss_after.png,
and counts near-white (RGB 240-254, alpha>200) pixels which are the
direct cause of the gray seam.
With the fix the near-white pixel count drops from 3714 to 133
(reduction of 96%), confirming the normalization removes the
artifact.
Assets like lose.png contain thousands of (254,254,254) pixels on a
background that is also white. When drawn on a white panel these
near-white pixels are slightly darker, producing a visible gray seam.
Apply a global normalization in load_image(): any opaque pixel with
R=G=B >= 250 is clamped to (255,255,255). Applied to every asset, not
only those with transparent_color, so the background is always pure
white regardless of off-by-one in the source PNG.
Replace the brittle 'self.position == other.position_before' check
with a symmetric cell-intersection test: two rats are considered
colliding if any of their current/previous cells overlap. This handles
both 'both rats in same cell' and 'one rat enters the cell the other
just left'.
Also remove the erroneous hasattr(other_unit, 'fuck') guard that
prevented Male.fuck() from being called on Female (Female has no fuck
method, but only Male should initiate reproduction).
Add tests/test_rat_reproduction.py with 8 scenarios covering overlapping
rats, baby rats, already-pregnant females, far-apart rats, female
self-initiation, sound playback, and procreate interval spawning.
- Add sdl2.create_overlay_texture() and draw_overlay_texture(alpha) for
transparent full-map overlays built from sub-tile surface blits.
- Add Graphics.regenerate_tunnel_cover() which builds an overlay of
4 random grass sub-tiles (20x20) for every internal tunnel cell,
defined as a tunnel cell surrounded by occupied cells (wall or
tunnel) on all four sides. Cells with at least one open side are
handled by cave_foreground and skipped here.
- Draw the tunnel cover in the game loop after top-layer units/effects
but before cave_foreground and points, at 95% opacity (alpha=242)
so the unit and effect passing underneath is just barely visible.
Remove the is_hidden_in_tunnel() checks from Explosion.draw() and
Gas.draw() so explosions and gas clouds remain visible when their center
falls inside a tunnel cell. Logic already spawned them in tunnel cells,
but they were rendered invisible because the draw path returned early.
Previously Rat.draw() hid any rat whose center fell inside a tunnel.
The clipping helper only handled single-entrance cells and returned None
for internal passages/crossroads, causing rats to vanish entirely.
Now internal tunnel cells (those with 0 or 2+ open sides) draw the rat
normally so it remains visible while walking through the tunnel.
Single-entrance cells keep the partial clip effect.
Also fix the visible-ratio math for DOWN/RIGHT clipping: the old
formulas subtracted cell_size from a local coordinate, producing
zero or negative visibility.
Previously regenerate_background() used occupied() (wall or tunnel) to
choose wall border/corner tiles. This caused walls adjacent to tunnels
to render with inner corners as if the tunnel were solid ground.
Now wall border logic uses is_wall() so only actual wall neighbors
influence the shape. Tunnel neighbor checks for flower-suppression are
kept explicit.
- Move gas poisoning from Gas.move() to Gas.collisions() so all rats are
registered in the collision system before the poison query runs.
- Shrink gas and explosion bboxes so rats must be well inside the tile to
be poisoned/killed.
- Use AABB overlap instead of partial_move threshold for gas poisoning.
- Draw mobile units first, then top-layer effects (gas, mines, bombs,
explosions) so rats appear under the gas.
- Add draw_on_top hint to Unit base class and top-layer units.
- Move Timer explosion from move() to collisions() so all units are
registered in the collision system before the kill query runs.
- Explosion units now set a bbox and kill rats that touch them.
- Guard Rat.draw() so dead rats are not drawn.
- Remove units/__pycache__ files from tracking.
- Introduced `test_final_level_flow.py` to validate final level transitions and game end scenarios.
- Created `test_game_over_flow.py` to ensure game over conditions trigger correctly based on rat counts.
- Implemented `test_keybindings.py` to verify keybinding configurations and their context-specific actions.
- Developed `test_level_editor.py` to assess level editor functionalities and layout computations.
- Added `test_level_io.py` for testing level data serialization and deserialization.
- Established `test_loop_logic_parity.py` to ensure consistent game state across multiple simulation runs.
- Created `test_non_regression.py` to simulate game behavior and capture states for future verification.
- Implemented `test_verify.py` to compare current game states against a golden master for regression detection.
- Introduced a new JSON file containing non-regression test states with detailed unit information, including positions, ages, and movement directions across multiple frames.
- Added a shell script for Bluetooth diagnostics that checks system information, Bluetooth binaries, running processes, D-Bus status, Bluetooth controller details, and audio stack status, providing a comprehensive overview for troubleshooting.
- Implemented a new Python script `mic_visualizer.py` that captures audio from a microphone and visualizes it in real-time.
- Utilized SDL2 for audio capture and rendering, allowing users to see waveform and spectrum representations of the audio input.
- Added command-line arguments for listing devices, selecting a capture device, and configuring window size and audio settings.
- Included functionality for displaying audio levels and peaks, enhancing user experience with visual feedback.
- Refactor keybindings for gas spawning across multiple configurations
- Implement new loading screen updates during game initialization
- Add tests to ensure all weapon actions are exposed in keybinding profiles
- Introduce new assets for explosion effects
- Added BMP_1_EXPLOSION_DOWN.png to original and preview directories.
- Added BMP_1_EXPLOSION_LEFT.png to original and preview directories.
- Added BMP_1_EXPLOSION_RIGHT.png to original and preview directories.
- Added BMP_1_EXPLOSION_UP.png to original and preview directories.
These assets are part of the explosion animation for the game, enhancing visual effects during gameplay.
- Added `image_clean.png` to the output directory for the clean image representation.
- Added `image_clean_preview.png` for the preview of the clean image.
- Introduced `image_svg_clean.png` for the SVG clean image representation.
Major improvements:
- NumPy-based collision system supporting 200+ units (~3ms/frame)
- Spatial hashing with vectorized distance calculations
- 4-pass game loop ensuring correct collision timing
- Blood overlay system with pre-generated stain pool
- Cached render positions and viewport bounds
- Spawn protection preventing rats spawning on weapons
Bug fixes:
- Fixed bombs not killing rats (collision system timing)
- Fixed gas not affecting rats (collision system timing)
- Fixed rats spawning on weapons (added has_weapon_at check)
- Fixed AttributeError with Gas collisions (added isinstance check)
- Fixed blood stain transparency (RGBA + SDL_BLENDMODE_BLEND)
- Reduced point lifetime from 200 to 90 frames (~1.5s)
- Blood layer now clears on game restart
Technical changes:
- Added engine/collision_system.py with CollisionLayer enum
- Updated all units to use collision layers
- Pre-allocate NumPy arrays with capacity management
- Hybrid collision approach (<10 simple, ≥10 vectorized)
- Python 3.13 compatibility
- Introduced a hybrid collision detection approach that utilizes NumPy for vectorized operations, improving performance for games with many entities (200+).
- Added a spatial grid for efficient lookups and AABB (Axis-Aligned Bounding Box) collision detection.
- Implemented a new `CollisionSystem` class with methods for registering units, checking collisions, and managing spatial data.
- Created performance tests to benchmark the new collision system against the old O(n²) method, demonstrating significant speed improvements.
- Updated existing code to integrate the new collision detection system and ensure compatibility with game logic.