New entity: Slime (EntityKind::Slime)
- 19 parts: blobby body (3x5) + 2 glowing eyes
- Green translucent colors, brighter center
- HP: 25, spawns every 45 ticks (max 2 alive)
Slime AI (update_slime_ai):
- Jumps toward player every 60 ticks when within 40 cells
- Jump power scales with proximity (closer = stronger)
- Pauses horizontally between jumps (50/50 hop-stop cycle)
- Uses set_horizontal_vel + set_vertical_vel (vector movement)
Combat system (update_combat):
- AABB overlap check between player and all alive enemies
- Goblin: 8 damage per 20 ticks on contact
- Slime: 5 damage per 20 ticks on contact
- Knockback: player pushed away from enemy on hit
- Player.take_damage() called, death possible from enemies
World gen: try_spawn_slime() spawns at surface, 18 cells from player
Renderers: slime rendered as 's' in ascii/terminal, green blob in graphics
6 new tests (122 total, 0 failures)
Redesigned entity from solid 5x5 block to humanoid silhouette:
Shape (23 bodies, was 27):
H H H (head, 3 wide)
H H H
A T T T A (shoulders + arms, 5 wide)
A T T T A
T T T (torso, 3 wide)
L R (legs, split with gap)
L R
Per-part colors (Noita-inspired):
- Player: head=bright yellow, torso=golden, arms=amber, legs=dark gold
- Goblin: head=bright green, torso=green, arms=olive, legs=dark green
- Corpse: desaturated browns
SubBody now has color: [u8; 4] field, set by build_humanoid.
All renderers (terminal, ascii, graphics) use b.color directly.
Fire effect: flickering orange overlay on burning entities.
half_w adjusted from 3.5 to 3.0, half_h from 2.5 to 3.5
(taller shape, narrower than old block).
109 tests, 0 failures
Each cell now stores its own fg/bg color inline, instead of looking
up from MaterialRegistry every frame.
Cell struct:
- fg: [u8; 3] — foreground color (for rendering)
- bg: [u8; 3] — background color (for rendering)
- temp: f32 — temperature (already there)
- material: MaterialId — for physics property lookup
- variant: u8 — visual/behavioral variant
- updated_this_tick: bool — CA flag
Colors are copied from MaterialRegistry at Cell::new() time.
This allows per-cell color variation in the future (water depth
shading, lava gradient, damaged materials, etc.) without registry
changes.
MaterialRegistry still stores physics properties (density, solid,
liquid, flammable, etc.) — looked up only during CA/physics steps,
not during rendering.
Renderers (terminal, ascii, graphics) updated to use cell.fg/cell.bg
directly — no more registry lookup in render path.
Cell size: ~16 bytes (was ~12). 250x250 grid = 1MB (was 750KB).
126 tests, 0 failures
Root cause of both issues: terminals don't send Release events by
default. When pressing W while holding A, terminal stops repeating A
(but doesn't send Release), so A times out and strafing breaks.
On key release, 400ms timeout means 400ms of extra movement.
Fix: PushKeyboardEnhancementFlags(REPORT_EVENT_TYPES) in terminal init.
This asks the terminal to send proper Press/Release/Repeat events for
ALL keys, not just special ones.
With Release events:
- Key release is instant (no 400ms timeout delay)
- Pressing W doesn't cancel A's held state (terminal sends Release
only when A is actually released)
- A/D strafing works while W is held
Fallback: if terminal doesn't support enhancement flags (old xterm),
got_release flag stays false and 150ms timeout is used. Once any
Release event is received, all keys switch to Release-based mode
(infinite timeout, rely on actual Release events).
109 tests, 0 warnings, 0 failures