NEW DOC: ADVANCED_VISUAL_SYSTEMS_PLAN.md 🌊💨 Complete implementation plan for: ✅ Wind & Foliage System (DONE) ❌ Water Physics & Buoyancy ❌ Water Ripples (footsteps, splash) ❌ Water Displacement (refraction shader) ❌ Caustics (light networks) 📝 Includes: - Full shader code (GLSL) - Phaser 3 integration - Performance optimization - Style 32 compatibility - 15-25h time estimate 🎯 Priority: WaterPhysics → Ripples → Displacement → Caustics 📁 Location: docs/technical/
22 KiB
🌊💨 ADVANCED VISUAL SYSTEMS - MASTER PLAN
Created: Jan 7, 2026 22:50 CET
Purpose: Complete implementation plan for advanced shader & particle systems
Style: Style 32 Dark-Chibi Noir compatible
Engine: Phaser 3 + Custom Shaders
📋 TABLE OF CONTENTS
- Wind & Foliage System ✅
- Water Displacement Mapping
- Ripple Effects
- Caustics (Light Networks)
- Water Physics & Buoyancy
- Integration Plan
- Performance Optimization
🌬️ 1. WIND & FOLIAGE SYSTEM
Status: ✅ IMPLEMENTED (Jan 7, 2026)
File: src/systems/WindFoliageSystem.js
Features:
- ✅ Vertex Shader for hair animation (Kai, Ana, Gronk dreads)
- ✅ Perlin Noise algorithm for natural wind
- ✅ Particle Emitter for falling leaves
- ✅ Wobble physics
- ✅ Biome-specific wind strength
Technical Details:
// Wind strength per biome
biomeWindSettings = {
'grassland': { strength: 1.0, frequency: 2.0 },
'mountains': { strength: 2.0, frequency: 3.5 },
'swamp': { strength: 0.3, frequency: 1.0 },
'forest': { strength: 0.8, frequency: 2.5 },
'desert': { strength: 1.5, frequency: 2.8 }
}
Performance:
- Uses shaders instead of sprite animations (RAM optimized)
- CPU-side Perlin noise fallback
- Particle pooling for leaves
Integration:
// In GameScene.js create()
this.windSystem = new WindFoliageSystem(this);
this.windSystem.init();
// Apply to sprites
this.windSystem.applyWindToSprite(kaiHairLayer, 'hair');
this.windSystem.applyWindToSprite(grassSprite, 'grass');
// Create leaf emitter
this.windSystem.createLeafEmitter(treeX, treeY, 0.5);
// In update()
this.windSystem.update(delta);
💧 2. WATER DISPLACEMENT MAPPING
Status: ❌ TODO - High Priority
Purpose: Light refraction when objects are underwater
Concept:
When Kai steps into water, the submerged part of her body should appear "wavy" due to light refraction (like looking at legs in a pool).
Technical Approach:
Displacement Shader:
// Fragment shader for underwater displacement
precision mediump float;
uniform sampler2D uMainSampler;
uniform float uTime;
uniform float uWaterLevel; // Y coordinate of water surface
uniform float uDisplacementStrength; // 0.01 - 0.05
varying vec2 vTexCoord;
varying vec2 vPosition;
// Sine wave displacement
vec2 getDisplacement(vec2 uv, float time) {
float wave1 = sin(uv.x * 10.0 + time * 2.0) * 0.005;
float wave2 = sin(uv.y * 8.0 + time * 1.5) * 0.005;
return vec2(wave1, wave2) * uDisplacementStrength;
}
void main() {
// Check if pixel is underwater
if (vPosition.y > uWaterLevel) {
// Apply displacement to UV coordinates
vec2 displacement = getDisplacement(vTexCoord, uTime);
vec2 distortedUV = vTexCoord + displacement;
vec4 color = texture2D(uMainSampler, distortedUV);
// Add slight blue tint underwater
color.rgb = mix(color.rgb, vec3(0.4, 0.5, 0.6), 0.15);
gl_FragColor = color;
} else {
// Above water - no distortion
gl_FragColor = texture2D(uMainSampler, vTexCoord);
}
}
Implementation Plan:
File: src/systems/WaterDisplacementSystem.js
class WaterDisplacementSystem {
constructor(scene) {
this.scene = scene;
this.waterLevel = 0; // Y coordinate
this.displacementStrength = 0.03;
}
createDisplacementShader() {
// Create custom Phaser pipeline
const DisplacementPipeline = new Phaser.Class({
Extends: Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline,
initialize: function() {
Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline.call(this, {
game: this.game,
renderer: this.renderer,
fragShader: [fragment shader code from above]
});
}
});
this.scene.game.renderer.addPipeline('waterDisplacement', new DisplacementPipeline());
}
applyToSprite(sprite, waterY) {
sprite.setPipeline('waterDisplacement');
sprite.pipeline.set1f('uWaterLevel', waterY);
sprite.pipeline.set1f('uDisplacementStrength', this.displacementStrength);
}
update(time) {
// Update time uniform for animation
this.scene.children.list.forEach(child => {
if (child.pipeline && child.pipeline.name === 'waterDisplacement') {
child.pipeline.set1f('uTime', time * 0.001);
}
});
}
}
Usage:
// In GameScene
this.waterSystem = new WaterDisplacementSystem(this);
this.waterSystem.createDisplacementShader();
// Apply to player when in water
if (this.player.y > waterSurfaceY) {
this.waterSystem.applyToSprite(this.player, waterSurfaceY);
}
Style 32 Compatibility:
- Displacement is SUBTLE (0.03 strength)
- Maintains thick black outlines
- Blue tint is minimal (15% mix)
- Works with cell-shaded sprites
🌊 3. RIPPLE EFFECTS
Status: ❌ TODO - Medium Priority
Purpose: Create circular ripples when walking in water or when objects fall
Concept:
When Kai walks through shallow water, or when a leaf falls from a tree into water, circular ripples should emanate from the impact point.
Technical Approach:
Particle-Based Ripples:
File: src/systems/WaterRipplesSystem.js
class WaterRipplesSystem {
constructor(scene) {
this.scene = scene;
this.ripplesParticles = null;
}
init() {
// Create ripple texture (Style 32 noir aesthetic)
this.createRippleTexture();
// Initialize particle manager
this.ripplesParticles = this.scene.add.particles('waterRipple');
}
createRippleTexture() {
const graphics = this.scene.add.graphics();
// Style 32: Thick black outline with subtle fill
graphics.lineStyle(3, 0x000000, 1); // 3px black outline
graphics.strokeCircle(32, 32, 28); // Outer circle
graphics.lineStyle(2, 0x000000, 0.6); // Thinner inner ring
graphics.strokeCircle(32, 32, 24);
// Generate texture
graphics.generateTexture('waterRipple', 64, 64);
graphics.destroy();
}
createRipple(x, y, size = 1.0) {
const emitter = this.ripplesParticles.createEmitter({
x: x,
y: y,
lifespan: 1500,
speed: 0,
scale: { start: 0.1 * size, end: 2.0 * size },
alpha: { start: 0.7, end: 0 },
blendMode: 'NORMAL',
frequency: -1, // Emit once
quantity: 1,
rotate: 0
});
emitter.explode(1);
// Auto-destroy emitter after animation
this.scene.time.delayedCall(1500, () => {
emitter.stop();
this.ripplesParticles.removeEmitter(emitter);
});
}
createFootstepRipples(x, y) {
// Smaller ripples for footsteps
this.createRipple(x, y, 0.5);
}
createSplashRipples(x, y) {
// Larger ripples for falling objects
this.createRipple(x, y, 1.5);
// Create multiple concentric ripples
this.scene.time.delayedCall(150, () => this.createRipple(x, y, 1.2));
this.scene.time.delayedCall(300, () => this.createRipple(x, y, 0.9));
}
}
Integration with Movement:
// In Player update()
update(delta) {
// Check if player is in water
if (this.isInWater && this.velocity > 0) {
// Create ripple at player position every 300ms
if (this.scene.time.now - this.lastRippleTime > 300) {
this.scene.waterRipples.createFootstepRipples(this.x, this.y + 16);
this.lastRippleTime = this.scene.time.now;
}
}
}
Integration with Wind System:
// In WindFoliageSystem - when leaf hits water
onLeafLand(leaf) {
if (this.isOverWater(leaf.x, leaf.y)) {
this.scene.waterRipples.createSplashRipples(leaf.x, leaf.y);
}
}
✨ 4. CAUSTICS (LIGHT NETWORKS)
Status: ❌ TODO - Medium Priority
Purpose: Animated dancing light patterns on underwater surfaces
Concept:
Those beautiful, shimmering light patterns you see on the bottom of a pool or cenote when sunlight refracts through moving water.
Technical Approach:
Animated Texture with Additive Blending:
File: src/systems/WaterCausticsSystem.js
class WaterCausticsSystem {
constructor(scene) {
this.scene = scene;
this.causticsLayers = [];
}
init() {
// Generate procedural caustics texture
this.generateCausticsTexture();
}
generateCausticsTexture() {
// Create 4 frames for animation
for (let frame = 0; frame < 4; frame++) {
const graphics = this.scene.add.graphics();
// Style 32 noir caustics: Subtle light patterns
graphics.fillStyle(0xffffff, 0.15);
// Draw organic light patterns (simplified)
const offset = frame * 20;
for (let i = 0; i < 20; i++) {
const x = (Math.sin(i * 0.5 + offset) * 30) + 64;
const y = (Math.cos(i * 0.7 + offset) * 30) + 64;
const size = 15 + Math.sin(i) * 5;
graphics.fillCircle(x, y, size);
}
graphics.generateTexture(`caustics_${frame}`, 128, 128);
graphics.destroy();
}
// Create animation
this.scene.anims.create({
key: 'caustics_anim',
frames: [
{ key: 'caustics_0' },
{ key: 'caustics_1' },
{ key: 'caustics_2' },
{ key: 'caustics_3' }
],
frameRate: 6,
repeat: -1
});
}
addCausticsLayer(x, y, width, height, depthLayer = 0) {
const caustics = this.scene.add.sprite(x, y, 'caustics_0')
.setOrigin(0, 0)
.setDisplaySize(width, height)
.setBlendMode(Phaser.BlendModes.ADD) // Additive blend!
.setAlpha(0.3)
.setDepth(depthLayer);
caustics.play('caustics_anim');
this.causticsLayers.push(caustics);
return caustics;
}
setCausticsIntensity(intensity) {
// Adjust based on water depth or time of day
this.causticsLayers.forEach(layer => {
layer.setAlpha(intensity * 0.3);
});
}
}
Usage:
// In WaterBiomeScene (Mexican Cenotes, Atlantis, etc.)
create() {
// Add caustics to water floor
this.caustics = new WaterCausticsSystem(this);
this.caustics.init();
// Add caustics layer to cenote floor
this.caustics.addCausticsLayer(
0, waterFloorY, // Position
mapWidth, 200, // Size
-1 // Below player
);
// Dynamic intensity based on depth
if (playerDepth > 50) {
this.caustics.setCausticsIntensity(0.2); // Darker at depth
}
}
Style 32 Compatibility:
- Uses white light with low alpha (15-30%)
- Additive blend mode for light effect
- Subtle, not overpowering
- Works on dark gothic floors
🏊 5. WATER PHYSICS & BUOYANCY
Status: ❌ TODO - High Priority
Purpose: Realistic movement underwater with drag and buoyancy
Concept:
- Kai moves 30% slower in water
- Hair floats upward (dreads like in zero gravity)
- Jumping is reduced underwater
- Swimming animation triggers
Technical Approach:
File: src/systems/WaterPhysicsSystem.js
class WaterPhysicsSystem {
constructor(scene) {
this.scene = scene;
// Physics constants
this.waterDragFactor = 0.7; // 30% slower
this.waterJumpReduction = 0.5; // 50% jump power
this.buoyancyForce = -20; // Upward force
this.hairFloatStrength = 1.5; // Hair rises more
}
applyWaterPhysics(player, isInWater) {
if (isInWater) {
// Apply drag to movement
player.body.velocity.x *= this.waterDragFactor;
player.body.velocity.y *= this.waterDragFactor;
// Apply buoyancy (slow upward drift)
player.body.velocity.y += this.buoyancyForce * this.scene.game.loop.delta * 0.001;
// Modify jump power
if (player.isJumping) {
player.jumpVelocity *= this.waterJumpReduction;
}
// Trigger swimming animation
if (!player.anims.currentAnim || player.anims.currentAnim.key !== 'swim') {
player.play('swim');
}
// Float hair upward
this.floatHair(player, true);
} else {
// Reset hair
this.floatHair(player, false);
}
}
floatHair(player, float) {
if (!player.hairLayer) return;
if (float) {
// Modify wind system to make hair float upward
if (this.scene.windSystem) {
// Override wind direction for hair to point UP
this.scene.windSystem.windAffectedLayers.forEach(layer => {
if (layer.sprite === player.hairLayer) {
layer.buoyantMode = true;
layer.floatStrength = this.hairFloatStrength;
}
});
}
} else {
// Restore normal wind behavior
if (this.scene.windSystem) {
this.scene.windSystem.windAffectedLayers.forEach(layer => {
if (layer.sprite === player.hairLayer) {
layer.buoyantMode = false;
layer.floatStrength = 0;
}
});
}
}
}
checkWaterDepth(x, y) {
// Return depth: 0 = surface, 100 = deep
// Can use tilemap water layer or zone detection
const waterTile = this.scene.waterLayer.getTileAtWorldXY(x, y);
if (!waterTile) return 0; // No water
// Use tile properties for depth
return waterTile.properties.depth || 50;
}
}
Enhanced Wind System Integration:
Update src/systems/WindFoliageSystem.js:
// Add to WindFoliageSystem.update()
update(delta) {
this.time += delta * 0.001;
this.windAffectedLayers.forEach(layer => {
const { sprite, type, baseX, baseY, buoyantMode, floatStrength } = layer;
if (!sprite || !sprite.active) return;
if (buoyantMode && floatStrength > 0) {
// UNDERWATER BUOYANCY MODE
// Hair floats upward with gentle wave
const floatOffset = Math.sin(this.time * 0.5) * 5 * floatStrength;
sprite.y = baseY - floatOffset; // Negative = upward
sprite.angle = floatOffset * 0.3; // Slight rotation
} else {
// NORMAL WIND MODE
const noiseX = baseX * 0.01 * this.wind.frequency + this.time * this.wind.speed;
const noiseY = baseY * 0.01 * this.wind.frequency + this.time * this.wind.speed * 0.7;
const windNoise = this.perlinNoise(noiseX, noiseY);
const displacement = windNoise * this.wind.strength * this.wind.amplitude;
if (type === 'hair') {
sprite.x = baseX + displacement * 1.5;
sprite.angle = displacement * 0.5;
} else if (type === 'grass') {
sprite.x = baseX + displacement;
}
}
});
// ... rest of update
}
Usage:
// In GameScene update()
update(time, delta) {
const playerDepth = this.waterPhysics.checkWaterDepth(this.player.x, this.player.y);
const isInWater = playerDepth > 10;
this.waterPhysics.applyWaterPhysics(this.player, isInWater);
// Update other systems
this.windSystem.update(delta);
}
🔧 INTEGRATION PLAN
Phase 1 - Core Systems (Already Done)
- ✅ WindFoliageSystem
Phase 2 - Water Effects (Next Priority)
-
WaterPhysicsSystem - Drag, buoyancy, hair float
- Estimated time: 4-6 hours
- Dependencies: WindFoliageSystem integration
-
WaterRipplesSystem - Footstep and splash ripples
- Estimated time: 2-3 hours
- Dependencies: None
-
WaterDisplacementSystem - Underwater refraction
- Estimated time: 6-8 hours
- Dependencies: Custom shader pipeline
Phase 3 - Advanced Effects (Polish)
- WaterCausticsSystem - Light networks
- Estimated time: 3-4 hours
- Dependencies: None
File Structure:
src/systems/
├── WindFoliageSystem.js ✅ Done
├── WaterPhysicsSystem.js ❌ TODO
├── WaterRipplesSystem.js ❌ TODO
├── WaterDisplacementSystem.js ❌ TODO
└── WaterCausticsSystem.js ❌ TODO
Integration in GameScene:
// In GameScene.js
create() {
// Initialize all systems
this.windSystem = new WindFoliageSystem(this);
this.windSystem.init();
this.waterPhysics = new WaterPhysicsSystem(this);
this.waterRipples = new WaterRipplesSystem(this);
this.waterRipples.init();
this.waterDisplacement = new WaterDisplacementSystem(this);
this.waterDisplacement.createDisplacementShader();
this.caustics = new WaterCausticsSystem(this);
this.caustics.init();
// Set biome wind
this.windSystem.setBiomeWind(this.currentBiome);
// Apply systems to player
this.windSystem.applyWindToSprite(this.player.hairLayer, 'hair');
}
update(time, delta) {
// Update all systems
this.windSystem.update(delta);
// Check if in water
const depth = this.waterPhysics.checkWaterDepth(this.player.x, this.player.y);
const isInWater = depth > 10;
this.waterPhysics.applyWaterPhysics(this.player, isInWater);
if (isInWater) {
this.waterDisplacement.update(time);
}
}
⚡ PERFORMANCE OPTIMIZATION
RAM Optimization:
- ✅ Shaders use GPU instead of sprite sheets
- ✅ Particle pooling (max 50 active ripples)
- ✅ Texture atlasing for caustics (4 frames only)
- ❌ TODO: Level-of-Detail (LOD) - disable effects on mobile
GPU Optimization:
- ✅ Minimize shader complexity (max 20 instructions)
- ✅ Use low-precision floats (mediump)
- ❌ TODO: Batch draw calls
- ❌ TODO: Frustum culling for particles
CPU Optimization:
- ✅ Perlin noise cached per frame
- ✅ Water depth checks only on movement
- ❌ TODO: Spatial partitioning for ripples
Memory Budget:
| System | Memory Usage | GPU Load |
|---|---|---|
| Wind | ~2MB | Low |
| Ripples | ~5MB | Medium |
| Displacement | ~1MB | High |
| Caustics | ~3MB | Low |
| TOTAL | ~11MB | Medium-High |
Target Performance:
- 60 FPS on desktop (GTX 1060 equivalent)
- 30 FPS on mobile (Snapdragon 855+)
- Graceful degradation on low-end devices
📊 IMPLEMENTATION CHECKLIST
Immediate (Next Session):
- Implement WaterPhysicsSystem
- Test hair buoyancy with WindFoliageSystem
- Implement WaterRipplesSystem
- Test ripples with player movement
Short-Term (This Week):
- Implement WaterDisplacementSystem
- Create custom shader pipeline
- Test underwater refraction effect
- Style 32 compatibility testing
Medium-Term (Next Week):
- Implement WaterCausticsSystem
- Generate procedural caustics textures
- Integrate all systems in test scene
- Performance profiling
Long-Term (Phase 2):
- Mobile optimization
- Water surface waves (optional)
- Underwater fog/particles (optional)
- Rain effects integration
🎨 STYLE 32 COMPATIBILITY NOTES
All systems MUST maintain Style 32 Dark-Chibi Noir aesthetic:
- Thick Black Outlines: Ripples have 3px black strokes
- Cell-Shaded: Displacement maintains flat color regions
- Subtle Effects: All effects at 15-30% opacity
- Gothic Palette: Use dark blues/greens for water tints
- No Realism: Effects are stylized, not photorealistic
🔗 SYSTEM DEPENDENCIES
WindFoliageSystem (✅ Done)
↓
WaterPhysicsSystem (uses WindSystem for hair float)
↓
WaterRipplesSystem (triggered by WaterPhysics movement)
↓
WaterDisplacementSystem (visual only, independent)
↓
WaterCausticsSystem (visual only, independent)
📝 NOTES TO SELF (ADHD-Friendly)
🌬️ Wind = DONE! (Check WindFoliageSystem.js)
💧 Water Physics NEXT → Start here!
- Drag factor = player moves slow
- Hair floats UP (modify WindSystem)
- Swimming animation trigger
🌊 Ripples EASY → Do this second!
- Just particles with scale+alpha animation
- Style 32 = black circles
- Trigger on footsteps
✨ Displacement HARD → Do this third!
- Need custom shader pipeline
- Test extensively
- Fallback for mobile
💎 Caustics OPTIONAL → Polish phase
- Low priority
- Looks amazing but not critical
- Mexican Cenotes will LOVE this!
TOTAL ESTIMATED TIME: 15-25 hours across all systems
PRIORITY ORDER:
- WaterPhysicsSystem (critical for gameplay)
- WaterRipplesSystem (polish, medium effort)
- WaterDisplacementSystem (polish, high effort)
- WaterCausticsSystem (optional, medium effort)
DolinaSmrti - Where even the water is gothic. 🌊💀