Void Breaker — Visual Overhaul & Gameplay Polish (vb-updates1)¶
Overview¶
This document is an exhaustive specification for the next round of Void Breaker updates. It addresses every known visual, gameplay, and environmental issue reported after the initial sprite integration — including broken heart pickups and a missing HP display — and adds significant new artistic elements to make the world feel like a living, breathing neon dystopian cityscape instead of a flat basic arena.
Do NOT rewrite the game from scratch. Refactor and extend current systems cleanly.
Table of Contents¶
1. Sprite Background Removal & Cropping¶
Problem¶
All current sprites at public/sprites/void-breaker/ are square PNGs with opaque backgrounds. The AI image generation produced full-scene images with colored/painted backgrounds instead of isolated sprites on transparent backgrounds. When drawn on the canvas, the entire rectangular PNG is visible — including the background — instead of just the cropped character/entity shape.
Current Sprite Inventory (all need fixing)¶
Path |
Entity |
Issue |
|---|---|---|
|
Player character |
Full scene PNG. The player character occupies only a portion of the image. The rest is painted background. |
|
Drifter / mini_drifter |
Square PNG with background visible |
|
Dasher |
Square PNG with background visible |
|
Orbiter |
Square PNG with background visible |
|
Tank |
Square PNG with background visible |
|
Splitter |
Square PNG with background visible |
|
Boss tiers 1, 2, 5 |
Square PNG with background visible |
|
Boss tiers 3, 4, 7 |
Square PNG with background visible |
|
Boss tiers 6, 8 |
Square PNG with background visible |
|
Heart pickup |
Square PNG with background visible |
Required Fix — Two-Part Solution¶
Part A: Re-generate All Sprites with Proper Prompts¶
Every sprite needs to be re-generated using the AI image generation tool. The key difference is the prompts MUST explicitly request:
“Isolated sprite on a solid black background” or “on a pure black (#000000) background” — black is the easiest to remove programmatically and looks best with additive blending.
“Centered in frame, no scenery, no environment” — prevents the AI from generating narrative scenes.
“Single object/character only” — prevents AI from generating multiple entities.
“Top-down / bird’s eye view” — enforces the correct perspective for the game.
“Clean edges, high contrast against background” — ensures easy separation.
Re-generation prompts for each sprite:
Player — void-runner.png (64×64 logical, generate at higher res):
Single top-down bird's eye view sprite of a cyberpunk mercenary character on a solid pure black background. Character wearing a dark trenchcoat with glowing teal cyan LED trim lines and a bright cyan visor over eyes. Character is centered in frame, facing right. No environment, no scenery, no floor. Single isolated character only. Clean edges, pixel art inspired, high contrast against the black background. Neon glow effect on edges.
Enemy — entity-drone-a.png (drifter):
Single top-down bird's eye view sprite of a small hovering drone on a solid pure black background. Circular orb shape with a single glowing magenta pink eye in center. Dark metallic body with subtle purple energy glow emanating from it. Centered in frame. No environment, no scenery. Single isolated object only. Clean edges, high contrast against black.
Enemy — void-strider.png (dasher):
Single top-down bird's eye view sprite of a fast angular attack creature on a solid pure black background. Sharp triangular aggressive shape like a glitch beast. Red and orange neon accent lines on dark body. Centered in frame. No environment, no scenery. Single isolated creature only. Clean edges, high contrast against black.
Enemy — shard-warden.png (orbiter):
Single top-down bird's eye view sprite of a floating sentinel on a solid pure black background. Diamond rhombus shape with rotating purple energy rings around it. Violet and purple neon glow. Dark shadow core. Centered in frame. No environment, no scenery. Single isolated object only. Clean edges.
Enemy — corrupt-hound.png (tank):
Single top-down bird's eye view sprite of a heavy armored enemy on a solid pure black background. Large bulky hexagonal shape with dark red armor plating and crimson neon trim accents. Looks heavy and imposing. Centered in frame. No environment, no scenery. Single isolated object only. Clean edges, high contrast.
Enemy — splitter.png:
Single top-down bird's eye view sprite of a biomechanical splitting creature on a solid pure black background. Irregular organic shape with green neon accent lines and veins glowing on dark body. Looks like it could break apart. Centered in frame. No environment, no scenery. Single isolated creature only. Clean edges.
Boss — harbinger.png:
Single top-down bird's eye view sprite of a massive fallen angel boss entity on a solid pure black background. Large winged construct with glowing red crimson energy core at center. Multiple floating dark armor plates arranged around central orb. Red and gold neon glow radiating outward. Angelic demonic fusion aesthetic. Centered in frame. No environment. Single isolated entity only. Clean edges, high contrast.
Boss — pattern-engine.png:
Single top-down bird's eye view sprite of a large mechanical boss construct on a solid pure black background. Geometric shape with rotating laser emitter appendages extending outward. Orange and amber neon accents on dark metallic body. Central targeting eye. Centered in frame. No environment. Single isolated entity only. Clean edges, high contrast.
Boss — void-regent.png:
Single top-down bird's eye view sprite of an ultimate ethereal void boss on a solid pure black background. Glitched fractured appearance with crown or halo of white energy above. Dark tendrils extending outward like void tentacles. White and blue-white glow emanating from body on pure black. Centered in frame. No environment. Single isolated entity only. Clean edges.
Pickup — heart.png:
Single top-down sprite of a glowing neon heart shape on a solid pure black background. Bright magenta pink heart with pulsating glow halo effect. Small, clean, perfectly centered in frame. No environment. Single isolated icon only. Clean crisp edges, high contrast against black.
After generation, copy each new image to its corresponding path in public/sprites/void-breaker/, overwriting the old square PNGs.
Part B: Update drawSprite.ts to Strip Black Backgrounds at Runtime¶
Even with perfect prompts, AI-generated images may still have some black fringing. The drawSprite.ts file needs an update to handle this:
Add an off-screen canvas background removal utility at the top of
drawSprite.ts(or in a new filelib/void-breaker/spriteUtils.ts):On first use of each sprite, create a temporary canvas.
Draw the image onto it.
Read pixel data via
getImageData().For every pixel where R, G, B are all below a threshold (e.g., < 30), set alpha to 0.
Apply a smooth alpha falloff for pixels near the threshold to avoid harsh edges.
Cache the resulting cleaned canvas/ImageBitmap and use THAT for all subsequent draws.
Important: this should only run once per sprite URL (cache the cleaned result).
Add a
SpriteConfig.removeBackgroundboolean (defaulttrue) to theSpriteConfiginterface insprites.ts. When true, the background removal is applied.Update
drawSprite()anddrawPickupSprite()to check for cleaned image cache first, falling back to raw image.
Files to Modify¶
lib/void-breaker/drawSprite.ts— Add background removal pipelinelib/void-breaker/sprites.ts— AddremoveBackgroundflag toSpriteConfiglib/void-breaker/SpriteLoader.ts— Optionally add agetCleanedImage()APIpublic/sprites/void-breaker/**/*— Replace all 10 sprite files
2. Projectile–Obstacle Collision (Buildings Block Shots)¶
Problem¶
Currently, projectiles (both player and enemy) pass straight through buildings and barriers. The updateProjectiles() method in game.ts (lines 908–917) only checks for out-of-bounds and lifetime — it does NOT check collisions with obstacles. This means buildings are purely visual cover; they don’t actually block shots.
Required Fix¶
In lib/void-breaker/game.ts, inside the updateProjectiles() method, after moving each projectile, add obstacle collision checks:
private updateProjectiles(dt: number): void {
for (const p of this.projectiles) {
if (!p.active) continue;
p.x += p.vx * dt;
p.y += p.vy * dt;
p.life -= dt;
// Out of bounds or expired
if (p.x < -20 || p.x > ARENA_W + 20 || p.y < -20 || p.y > ARENA_H + 20 || p.life <= 0) {
p.active = false;
continue;
}
// NEW: Check collision with solid obstacles (buildings, barriers, debris)
for (const o of this.obstacles) {
if (!o.active) continue;
if (o.type === 'hazard' || o.type === 'terminal') continue; // hazards and terminals don't block
if (circleAABBOverlaps(p.x, p.y, p.radius, o.x, o.y, o.w, o.h)) {
p.active = false;
// Spawn a small hit spark particle at impact point
this.spawnParticles(p.x, p.y, p.isPlayer ? '#44ddff' : '#ff00cc', 3, 60);
// If obstacle is destructible, damage it
if (o.destructible) {
o.hp -= p.damage;
if (o.hp <= 0) {
o.active = false;
this.spawnParticles(
o.x + o.w / 2, o.y + o.h / 2,
'#888888', 10, 100
);
}
}
break;
}
}
}
}
Import required: The circleAABBOverlaps function is already exported from mapSystem.ts. Make sure it’s imported at the top of game.ts:
import { circleAABBOverlaps } from './mapSystem';
This function already exists and is used for player/enemy obstacle collision. We just need to also use it for projectiles.
Files to Modify¶
lib/void-breaker/game.ts—updateProjectiles()method, add import
3. Heart Pickup Fix — Collection & Healing¶
Problem¶
Heart pickups drop from enemies (8% chance on kill), render on screen, and have magnet pull toward the player — but they do not actually heal the player when collected. The player walks over them and nothing happens.
Root Cause Analysis¶
There are multiple bugs in updateHeartPickups() at lib/void-breaker/game.ts (lines 1299–1328):
Bug 1: Stale distance used for collision check¶
// Line 1308: distance is computed BEFORE magnet pull
const d = dist(h.x, h.y, p.x, p.y);
if (d < HEART_MAGNET_RANGE) {
// ... magnet moves h.x, h.y ...
}
// Line 1316: 'd' is STILL the old value — heart may have moved closer!
if (d < p.radius + 12 && p.hp < p.maxHp) {
After the magnet pull updates h.x and h.y, the collision check on line 1316 still uses the old distance d from line 1308. If the magnet pulled the heart close enough to overlap in this tick, the collision is missed because d is the pre-pull distance.
Bug 2: Collection gated on p.hp < p.maxHp¶
if (d < p.radius + 12 && p.hp < p.maxHp) {
The heart pickup can only be collected if the player’s HP is below max. If the player is at full health, the heart just floats there forever (or until its 12-second lifetime expires). This is unintuitive — the heart should always be collectible but the heal should only apply if HP is below max. Even at full HP, the heart should be consumed with a visual/audio cue.
Bug 3: Collision radius is too small¶
The check uses p.radius + 12. The player radius is 10 (from PLAYER_RADIUS in constants.ts), giving a total collision radius of 22 game units. With the magnet range of 60, the heart gets pulled in but the 22-unit window is very tight — especially when moving fast. This should be at least p.radius + 18 for comfortable pickup feel.
Required Fix¶
Replace the entire updateHeartPickups() method in game.ts:
private updateHeartPickups(dt: number): void {
const p = this.player;
for (const h of this.heartPickups) {
if (!h.active) continue;
h.lifetime -= dt;
if (h.lifetime <= 0) { h.active = false; continue; }
// Magnet pull toward player
let d = dist(h.x, h.y, p.x, p.y);
if (d < HEART_MAGNET_RANGE && d > 1) {
const [nx, ny] = norm(p.x - h.x, p.y - h.y);
h.x += nx * HEART_PULL_SPEED * dt;
h.y += ny * HEART_PULL_SPEED * dt;
// IMPORTANT: Recompute distance AFTER magnet pull
d = dist(h.x, h.y, p.x, p.y);
}
// Collision with player — ALWAYS collect, heal if HP not full
const pickupRadius = p.radius + 18;
if (d < pickupRadius) {
h.active = false;
this.spawnParticles(h.x, h.y, '#ff00cc', 8, 100);
if (p.hp < p.maxHp) {
// Heal the player
p.hp = Math.min(p.hp + HEART_HEAL_AMOUNT, p.maxHp);
this.popups.push({
text: '+1 HP',
x: p.x, y: p.y - 25,
life: 0.8, maxLife: 0.8,
color: '#ff00cc',
});
} else {
// Already at full HP — still collect, show feedback
this.popups.push({
text: 'FULL HP',
x: p.x, y: p.y - 25,
life: 0.6, maxLife: 0.6,
color: '#888888',
});
}
}
}
}
Key Changes Summary¶
Bug |
Fix |
|---|---|
Stale distance |
Recompute |
Full HP gating |
Always collect the heart; heal only if |
Tight collision |
Increase pickup radius from |
Edge case: |
Prevent division-by-zero in |
Files to Modify¶
lib/void-breaker/game.ts— ReplaceupdateHeartPickups()method
4. Player HP Display — Visible Health Indicator¶
Problem¶
The player currently has no way to see their current health during gameplay. There is no HP bar, no health number, and no visual indicator on the canvas or HUD overlay. The React HUD overlay in VoidBreakerGame.tsx (lines 452–464) renders HP as diamond symbols (♦) — but these are:
Extremely small (text-sm / text-base CSS) and easy to miss during combat.
Using a diamond ♦ symbol which doesn’t read as “health” — players expect hearts (♥) or a life bar.
Tucked into a corner with no label — new players won’t recognize it as HP.
Not visible on the canvas itself — the renderer (
renderer.ts) draws zero HP information.
Required Fix — Three-Part Solution¶
Part A: Add HP Bar to Canvas Renderer (renderer.ts)¶
Draw a persistent HP bar directly on the canvas below the player character, so it’s always visible during gameplay regardless of the HUD overlay:
// Inside the Player rendering section (after drawing the player sprite/circle)
// ── Player HP Bar (always visible, drawn under player) ───────────────────
if (game.state === 'playing') {
const hpBarWidth = 40 * scale;
const hpBarHeight = 4 * scale;
const hpBarX = playerPos.x - hpBarWidth / 2;
const hpBarY = playerPos.y + pr + 6; // below the player
const hpFraction = p.hp / p.maxHp;
// Background bar
ctx.fillStyle = 'rgba(20, 20, 30, 0.7)';
ctx.fillRect(hpBarX, hpBarY, hpBarWidth, hpBarHeight);
// HP fill — color changes based on health level
let hpBarColor: string;
if (hpFraction > 0.6) {
hpBarColor = '#00ff88'; // green — healthy
} else if (hpFraction > 0.3) {
hpBarColor = '#ffaa00'; // amber — caution
} else {
hpBarColor = '#ff2244'; // red — danger
}
ctx.fillStyle = hpBarColor;
ctx.shadowColor = hpBarColor;
ctx.shadowBlur = 6;
ctx.fillRect(hpBarX, hpBarY, hpBarWidth * hpFraction, hpBarHeight);
// Border
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
ctx.lineWidth = 0.5;
ctx.strokeRect(hpBarX, hpBarY, hpBarWidth, hpBarHeight);
ctx.shadowBlur = 0;
// Numeric text: "3/5" style
ctx.font = `bold ${Math.ceil(7 * scale)}px monospace`;
ctx.fillStyle = hpBarColor;
ctx.textAlign = 'center';
ctx.fillText(`${p.hp}/${p.maxHp}`, playerPos.x, hpBarY + hpBarHeight + 9);
ctx.textAlign = 'left';
}
This HP bar should be drawn after the player sprite (so it’s on top) and before the ally section. Place it inside the if (!isInvincible) block or right after it — it should always be visible (even during invincibility blinks, the bar stays solid).
If the player is invincible (blinking), the main player sprite blinks but the HP bar should remain visible and steady. So this code should go outside the if (!isInvincible) block.
Part B: Improve React HUD HP Display (VoidBreakerGame.tsx)¶
Replace the current diamond-based HP display (lines 452–464 in VoidBreakerGame.tsx) with a more visible, labeled heart-based display:
{/* HP hearts */}
<div className="bg-black/70 rounded px-2 py-1 text-right border border-[#00f5ff]/20 backdrop-blur-sm">
<div className="text-[9px] font-mono text-zinc-500 mb-0.5">HP</div>
<div className="text-sm sm:text-base flex items-center gap-0.5">
{Array.from({ length: hud.maxHp }, (_, i) => (
<span key={i} className={i < hud.hp
? 'text-[#ff00cc] drop-shadow-[0_0_6px_rgba(255,0,204,0.8)]'
: 'text-zinc-800'
}>
{'♥'}
</span>
))}
<span className="text-[10px] font-mono text-zinc-500 ml-1">
{hud.hp}/{hud.maxHp}
</span>
</div>
</div>
Key changes:
Changed ♦ to ♥ — universal health symbol.
Added “HP” label above the hearts so new players immediately understand it.
Added numeric
3/5readout next to the hearts for precision.
Part C: HP Flash Effect on Damage (renderer.ts)¶
When the player takes damage, briefly flash the HP bar red and pulse it larger for ~300ms to draw attention:
// If player was recently hit, pulse the HP bar
const recentlyHit = game.elapsedMs < p.hitFlashUntil + 200;
if (recentlyHit) {
const flashAlpha = 0.3 + Math.sin(game.elapsedMs * 0.02) * 0.3;
ctx.fillStyle = `rgba(255, 0, 50, ${flashAlpha})`;
ctx.fillRect(hpBarX - 2, hpBarY - 1, hpBarWidth + 4, hpBarHeight + 2);
}
Part D: HP Change Popup Feedback (already partially implemented)¶
The existing popup system already shows +1 HP when a heart is picked up (once the fix in Section 3 is applied). However, there is no popup when the player takes damage. Add a damage popup in damagePlayer():
private damagePlayer(dmg: number): void {
const p = this.player;
p.hp -= dmg;
p.hitFlashUntil = this.elapsedMs + 80;
p.invincibleUntil = this.elapsedMs + INVINCIBILITY_MS;
this.triggerShake(5, 300);
this.spawnParticles(p.x, p.y, '#ff4444', 10, 120);
// NEW: Show damage number popup
this.popups.push({
text: `-${dmg} HP`,
x: p.x, y: p.y - 30,
life: 1.0, maxLife: 1.0,
color: '#ff2244',
});
if (p.hp <= 0) { p.hp = 0; this.state = 'gameOver'; }
}
Files to Modify¶
lib/void-breaker/renderer.ts— Add HP bar below player, add HP flash effectcomponents/void-breaker/VoidBreakerGame.tsx— Improve HUD HP display (♦→♥, add label, add numeric readout)lib/void-breaker/game.ts— Add damage popup indamagePlayer()
5. Building Sprites & Environmental Art¶
Problem¶
Buildings and obstacles are currently rendered as simple flat-colored rectangles (lines 199–259 of renderer.ts). The building type draws as #111118 filled rect, debris as #1a1420, etc. This looks extremely basic and breaks the neon dystopian immersion.
Required Changes¶
3A: Generate Building Sprites¶
Generate new building/obstacle sprites and save them to public/sprites/void-breaker/buildings/:
File |
Description |
Prompt |
|---|---|---|
|
Tall Kowloon building, top-down |
|
|
Wide Kowloon building, top-down |
|
|
Industrial pipe cluster |
|
|
Void core pillar |
|
|
Rubble/debris pile |
|
|
Low wall barrier |
|
3B: Add Building Sprite Config to sprites.ts¶
Add a new BUILDING_SPRITES registry to sprites.ts:
export const BUILDING_SPRITES: Record<string, Record<string, SpriteConfig>> = {
kowloon: {
building: {
url: '/sprites/void-breaker/buildings/building-kowloon-1.png',
anchorX: 0, anchorY: 0, scale: 1, rotationMode: 'none',
removeBackground: true,
},
debris: {
url: '/sprites/void-breaker/buildings/debris-1.png',
anchorX: 0, anchorY: 0, scale: 1, rotationMode: 'none',
removeBackground: true,
},
barrier: {
url: '/sprites/void-breaker/buildings/barrier-1.png',
anchorX: 0, anchorY: 0, scale: 1, rotationMode: 'none',
removeBackground: true,
},
},
industrial: {
building: {
url: '/sprites/void-breaker/buildings/building-industrial-1.png',
anchorX: 0, anchorY: 0, scale: 1, rotationMode: 'none',
removeBackground: true,
},
// ... etc
},
void_core: {
building: {
url: '/sprites/void-breaker/buildings/building-voidcore-1.png',
anchorX: 0, anchorY: 0, scale: 1, rotationMode: 'none',
removeBackground: true,
},
// ... etc
},
};
3C: Update Obstacle Rendering in renderer.ts¶
In the // ── 8. Obstacles section (lines 199–259 of renderer.ts), for each obstacle type, attempt to draw the building sprite first and fall back to the current rectangle rendering:
case 'building': case 'barrier': {
// Try sprite first
const theme = game.currentMapConfig.theme; // 'kowloon' | 'industrial' | 'void_core'
const buildingSpriteConfig = BUILDING_SPRITES[theme]?.[o.type];
const img = buildingSpriteConfig ? getCachedImage(buildingSpriteConfig.url) : undefined;
if (img) {
ctx.drawImage(img, otl.x, otl.y, orw, orh);
// Neon border outline
ctx.strokeStyle = game.currentMapConfig.borderColor + '55';
ctx.lineWidth = 1;
ctx.strokeRect(otl.x, otl.y, orw, orh);
} else {
// Fallback: existing rectangle rendering
ctx.fillStyle = '#111118';
ctx.fillRect(otl.x, otl.y, orw, orh);
ctx.strokeStyle = game.currentMapConfig.borderColor + '55';
ctx.lineWidth = 1;
ctx.strokeRect(otl.x, otl.y, orw, orh);
}
break;
}
Also add aesthetic enhancements to the rectangle fallback:
Add window lights: Draw small 2×3px rectangles in random positions on buildings using the map’s ambient glow color at low alpha.
Add rooftop details: Draw subtle line patterns (antennas, vents) on top of building rectangles.
Add a slight gradient instead of flat fill to give buildings depth.
Files to Modify¶
lib/void-breaker/sprites.ts— AddBUILDING_SPRITESregistry, addremoveBackgroundtoSpriteConfiglib/void-breaker/renderer.ts— Update obstacle draw sectionpublic/sprites/void-breaker/buildings/— New directory with ~6 building sprites
New Files¶
public/sprites/void-breaker/buildings/*.png(6+ building sprites)
6. Neon Billboards & Signage¶
Problem¶
The arena world feels empty and generic. A real neon dystopian city like Kowloon Walled City or Chungking Mansions would have neon signs, holographic billboards, and glowing advertisements everywhere.
Required Implementation¶
4A: Add Billboard Obstacle Type¶
Add 'billboard' to the Obstacle type union in mapSystem.ts:
type: 'building' | 'debris' | 'barrier' | 'tree' | 'terminal' | 'hazard' | 'billboard';
Billboards should be non-collidable (entities and projectiles pass through them) — they’re purely decorative. Handle this in the collision code by skipping billboard type.
4B: Add Billboards to Map Configs¶
Add billboard obstacles to each map config in mapSystem.ts. Billboards should be placed on or near buildings, at the edges of the arena, and in strategic visual positions:
// Map 1 (Kowloon) billboards
{ x: 90, y: 55, w: 80, h: 30, type: 'billboard', destructible: false, hp: 999, maxHp: 999, glowColor: '#00f5ff' },
{ x: 1430, y: 55, w: 80, h: 25, type: 'billboard', destructible: false, hp: 999, maxHp: 999, glowColor: '#ff00cc' },
{ x: 700, y: 55, w: 50, h: 20, type: 'billboard', destructible: false, hp: 999, maxHp: 999, glowColor: '#ff6820' },
// Add at least 4-6 per map
4C: Render Billboards with Neon Effects¶
In renderer.ts, add a new case in the obstacle rendering switch for 'billboard':
case 'billboard': {
const bGlow = o.glowColor ?? NEON_CYAN;
const bPulse = 0.7 + Math.sin(t * 2 + o.id * 1.3) * 0.3;
// Billboard background (dark panel)
ctx.fillStyle = '#0a0a18';
ctx.fillRect(otl.x, otl.y, orw, orh);
// Glowing border
ctx.strokeStyle = bGlow;
ctx.lineWidth = 1.5;
ctx.shadowColor = bGlow;
ctx.shadowBlur = 15 * bPulse;
ctx.strokeRect(otl.x, otl.y, orw, orh);
// Inner neon text (rotating pseudo-random "ads")
const adTexts = ['零号区', 'VOID™', '夜市', 'ネオン', 'RUN.', '虚空', 'BREACH', '堕落'];
const adIndex = o.id % adTexts.length;
ctx.font = `bold ${Math.ceil(Math.min(orh * 0.6, 10 * scale))}px monospace`;
ctx.fillStyle = bGlow + Math.floor(bPulse * 200 + 55).toString(16).padStart(2, '0');
ctx.textAlign = 'center';
ctx.fillText(adTexts[adIndex], otl.x + orw / 2, otl.y + orh * 0.7);
ctx.textAlign = 'left';
// Occasional flicker
if (Math.sin(t * 7 + o.id * 3.7) > 0.92) {
ctx.fillStyle = bGlow + '22';
ctx.fillRect(otl.x, otl.y, orw, orh);
}
ctx.shadowBlur = 0;
break;
}
Files to Modify¶
lib/void-breaker/mapSystem.ts— Add'billboard'to type, add billboard obstacles to MAP_1, MAP_2, MAP_3lib/void-breaker/renderer.ts— Add billboard rendering caselib/void-breaker/game.ts— Skip'billboard'in collision checks (updateresolveObstacleCollisioncall andupdateProjectiles)
7. Background & World Artistic Enhancements¶
Problem¶
The background and world are too minimalistic. The parallax city silhouettes are simple rectangles, the arena floor is flat, and there’s no sense of place. The game should feel like a living cyberpunk city.
Required Enhancements¶
5A: Enhanced Parallax City Silhouettes (renderer.ts)¶
The current drawCitySilhouettes() (lines 565–582) draws basic rectangles. Enhance this method:
Vary building shapes: Some buildings should have antenna spikes, rooftop equipment silhouettes, or stepped profiles instead of flat rectangles.
Add neon sign patches: On some buildings, draw small colored rectangles (simulating lit signs) using magenta, cyan, and orange.
Add vertical light beams: Occasionally draw thin vertical lines from building tops (searchlights/holographic beams) with very low alpha.
Add a rain effect: Draw faint diagonal lines across the background to simulate neon-lit rain. This is thematic (the specs mention “the rain never stops in the void zones”). Implementation:
// Rain effect — subtle diagonal streaks ctx.strokeStyle = 'rgba(100, 200, 255, 0.04)'; ctx.lineWidth = 0.5; for (let i = 0; i < 40; i++) { const rx = (seed(i * 17) * CANVAS_WIDTH + t * 60) % CANVAS_WIDTH; const ry = (seed(i * 23) * CANVAS_HEIGHT + t * 120) % CANVAS_HEIGHT; ctx.beginPath(); ctx.moveTo(rx, ry); ctx.lineTo(rx - 3, ry + 15); ctx.stroke(); }
Add parallax depth layers: Currently there’s one layer. Add a second, more distant layer with smaller, dimmer buildings to create real depth.
5B: Arena Floor Detail (renderer.ts)¶
The arena floor is currently a single flat fillRect. Enhance it:
Add floor crack patterns: Draw faint crack-like line segments across the floor using the grid color with reduced alpha.
Add puddle reflections: Randomly placed semi-transparent ellipses (simulating neon reflections in standing water) using the map’s ambient glow color at very low alpha.
Add subtle tile pattern: Instead of uniform grid spacing, vary the grid slightly to give it a broken tile feel.
Add glowing seams: Every 4th grid line should be slightly brighter (suggesting infrastructure below the floor).
5C: Void Dust Enhancement (renderer.ts)¶
The current void dust particles are fine but could be enhanced:
Add varying colors: Instead of all cyan, some particles should be magenta, some white, some map-themed.
Add size variation: Larger, slower particles mixed with smaller, faster ones.
Add brief glow flare: Some particles should briefly flare (increase size/alpha) then fade back.
5D: Enhanced Vignette & Atmosphere (renderer.ts)¶
Make the vignette color map-themed: Currently it’s pure black. For the industrial map, tint it slightly magenta. For void core, tint it deep red.
Add chromatic aberration: On boss encounters, add a subtle RGB split effect (draw the game with slight offset for red and blue channels). This can be computationally expensive — do it only during boss phases by applying offset translations to a saved canvas state.
Add screen edge glow during ability usage: When Phase Shift is active, pulse white glow from edges inward. When Reflect Shield is active, pulse green from edges.
5E: Enhanced Building/Obstacle Artistic Details (renderer.ts)¶
Even with fallback rectangle rendering (before sprites load), make buildings look dramatically better:
Window light grid: On every building, procedurally draw a grid of tiny (2×3px) window lights. Use a seeded random per-building-ID to determine which windows are lit. Colors: mix of warm yellow, cool cyan, and occasional magenta.
Rooftop neon strip: Draw a thin neon line along one edge of each building (simulating rooftop lighting).
Shadow offset: Draw a slightly offset dark rectangle behind each building to give it depth/shadow.
Damaged look: For buildings on Map 3 (Void Core), add crack lines and broken sections.
Files to Modify¶
lib/void-breaker/renderer.ts— Major enhancements todrawCitySilhouettes(), arena floor rendering, void dust, vignette, obstacle renderingNEW: Consider adding a
lib/void-breaker/worldEffects.tsutility file for rain, reflections, and other atmospheric effects ifrenderer.tsgets too large
8. File Manifest & Architecture¶
Files to Modify¶
File |
Changes |
|---|---|
|
Add runtime background removal pipeline using off-screen canvas + pixel manipulation |
|
Add |
|
Add |
|
Enhanced obstacle rendering with sprites, billboard rendering, improved city silhouettes, floor details, rain effect, better vignette, window lights, player HP bar on canvas, HP flash on damage |
|
Add obstacle collision to |
|
Add |
|
Improve HUD HP display: ♦→♥, add “HP” label, add numeric readout |
New Files¶
File |
Purpose |
|---|---|
|
Building sprite for Kowloon theme |
|
Building sprite variant |
|
Building sprite for Industrial theme |
|
Building sprite for Void Core theme |
|
Debris pile sprite |
|
Barrier wall sprite |
Overwritten Files (re-generated with proper prompts)¶
File |
Notes |
|---|---|
|
Re-generate with black bg, crop prompt |
|
Re-generate |
|
Re-generate |
|
Re-generate |
|
Re-generate |
|
Re-generate |
|
Re-generate |
|
Re-generate |
|
Re-generate |
|
Re-generate |
9. Acceptance Criteria¶
Sprites¶
All entity sprites render without any visible square background
Player sprite shows only the cropped character shape with transparent/removed background
Enemy sprites show only the entity shape, not the full PNG rectangle
Boss sprites show only the boss shape with glow, not a square PNG
Heart pickup renders as just the heart shape with neon glow
Background removal works at runtime even if AI produces slight fringing
Heart Pickups¶
Heart pickups actually heal the player on collection (the core bug is fixed)
Hearts are collectible even when player is at full HP (shows “FULL HP” text)
Magnet pull correctly draws hearts toward the player within 60 game units
Collision radius is generous enough that hearts don’t pass through the player
Distance is recomputed after magnet pull so same-frame collection works
Pick-up produces magenta particle burst + “+1 HP” popup text
Hearts despawn after 12 seconds with a visible alpha fade
Player HP Display¶
Player HP bar is visible on the canvas directly below the player character at all times during gameplay
HP bar color changes dynamically: green (>60%), amber (30–60%), red (<30%)
Numeric HP readout (e.g. “3/5”) is displayed below the HP bar on canvas
HP bar stays visible and steady even during invincibility blinks
React HUD overlay uses ♥ hearts instead of ♦ diamonds
React HUD has an “HP” label above the hearts
React HUD shows numeric HP readout (e.g. “3/5”) next to hearts
HP bar flashes red briefly when player takes damage
A red damage popup (e.g. “-1 HP”) appears above the player when hit
Projectile Collision¶
Player projectiles hitting a building/barrier/debris obstacle → projectile destroyed + spark particles
Enemy projectiles hitting a building/barrier/debris obstacle → projectile destroyed + spark particles
Destructible obstacles (trees, some debris) take damage from projectiles and can be destroyed
Billboards do NOT block projectiles (pass-through)
Hazards do NOT block projectiles (pass-through)
Terminals do NOT block projectiles (pass-through)
Building Sprites¶
Buildings render with sprites instead of flat rectangles (when sprite loaded)
Fallback rectangle rendering has window lights, gradients, and rooftop details
Different building sprites for different map themes (Kowloon, Industrial, Void Core)
Building sprites have transparent/removed backgrounds
Billboards¶
At least 4 neon billboards visible per map
Billboards display rotating Chinese/Japanese/English text
Billboards have neon glow pulsing effect
Billboards occasionally flicker
Billboards are non-collidable (decorative only)
World Art¶
Parallax city silhouettes have varied shapes (not all flat rectangles)
Some silhouette buildings have neon sign patches
Rain effect visible in background (subtle diagonal streaks)
Arena floor has crack patterns and/or puddle reflections
Void dust particles have color variety
Vignette tint matches current map theme
Overall visual impression is “living cyberpunk city” not “flat arena”
Performance¶
All background removal only runs once per sprite (cached)
No frame rate drops below 50 FPS during normal gameplay
Rain and atmospheric effects use minimal draw calls
Building window lights use seeded random (not Math.random per frame — would cause flickering)
Implementation Order¶
Fix heart pickup bugs in
game.ts→ fix distance recomputation, remove full-HP gate, increase pickup radiusAdd player HP display → canvas HP bar in
renderer.ts, improve React HUD HP inVoidBreakerGame.tsx, add damage popupRe-generate all 10 entity/pickup sprites with proper black-background prompts → copy to
public/sprites/void-breaker/Implement runtime background removal in
drawSprite.ts→ verify sprites render without square backgroundsAdd projectile–obstacle collision in
game.ts→ verify projectiles are blocked by buildingsGenerate building sprites → copy to
public/sprites/void-breaker/buildings/Add billboard type to
mapSystem.ts→ add billboard obstacles to all mapsUpdate obstacle rendering in
renderer.ts→ sprite buildings, billboard rendering, window lightsEnhance background art → city silhouettes, rain, floor details, vignette
Polish and test → verify all acceptance criteria