Play Instigator

Blog

Dev progress on the left. Personal thoughts on the right.

Dev Log

AI-generated · automated

I'm a solo dev working part-time. No time for proper devlogs, so I automated them. Every week, a local AI reads my raw session notes and writes a summary. No editorial polish — just proof the game is alive.

Musings

Thoughts on making this game, running a studio alone, and whatever else comes up.

In the latest dev session, we made some cool tweaks to Ritual & Ruin that should brighten up your experience! The chunk floor shader got a much-needed fix for those annoying white streaks that used to appear when moving around. We shifted our shadow calculations to work on a per-pixel basis, meaning smoother and more seamless shadows across the game world—no more glaring stripes where they shouldn't be!

We've also pushed Ritual & Ruin further into its dark kawaii aesthetic, which you might recognize as having that distinct anime vibe. The floor tiles now have a more dramatic contrast with near-white tops accented by violet hints, and darker sides bordered in dim purple grout. It's all about striking a balance between the cute and the mysterious.

On top of these changes, we've polished up the jellyfish players. They're sporting longer tentacles that extend their reach within matches, making every move more pronounced. Plus, they've become more visible against darker floors with increased opacity—so no more squinting to spot your jellyfish on the battlefield! All these tweaks aim to make the visuals pop while ensuring everything looks sharp and cohesive. Can't wait for you all to see how it turns out in action!

Raw session notes

2026-04-07 — Chunk Floor Shader, Shadow Fix, Visual Polish

Summary

Fixed a persistent white-streak artifact on the chunk floor shader, iterated on the dark kawaii visual direction, and polished jellyfish player visuals.

Dark Kawaii Visual Direction

Iterated the ChunkFloorTile shader toward a dark kawaii / anime aesthetic:

  • Top face: near-white tiles with violet hint (0.93, 0.90, 1.00) + neon lavender grout (0.72, 0.38, 0.95) with glow emission
  • Side faces: near-black tiles (0.05, 0.05, 0.09) + dim purple grout (0.48, 0.36, 0.70)
  • Face detection: branchless yDom = step(absNorm.x, absNorm.y) * step(absNorm.z, absNorm.y) drives both color palettes
  • Degradation: Voronoi crack lines with pink/magenta glow (0.95, 0.18, 0.62), FBM staining, mold growth
  • Crack scale: lowered _CrackScale to 0.5 (few large cracks rather than fine network)
  • Tile scale: doubled _TileScale to 1.0 (larger tiles)
  • Cel shading: floor(NdotL * bands) / (bands - 1) quantized diffuse, 3 bands

---

Visual Polish

Jellyfish Tentacles — Longer

  • segmentLength: 0.150.35 (scale=1 normalized, auto-scaled at runtime)
  • At match scale=0.25: total tentacle reach ≈ 0.525 world units (was 0.225)

Jellyfish Body — More Opaque

  • _Opacity in JellyfishBody.mat: 0.180.65
  • Players are now clearly visible against the dark floor

---

Files Changed

  • Assets/ProtoV2/Shaders/ChunkFloorTile.shader — per-fragment shadow coord, dark kawaii style
  • Assets/ProtoV2/Materials/ChunkFloorTile.mat — color values for dark kawaii look
  • Assets/ProtoV2/Scripts/JellyfishVisuals.cs — segmentLength 0.15→0.35
  • Assets/ProtoV2/Materials/JellyfishBody.mat — _Opacity 0.18→0.65

### Summary of Recent Changes and Developments

#### Jellyfish Tentacle Reversion (2026-04-03)

**Issue:** - The implementation of ObiRope tentacles for jellyfish caused excessive bouncing during bell pulses, as they reacted to the physics of the visual root squishing.

**Solution:** - Reverted to using a LineRenderer spring-chain setup. - Adjusted segment lengths and scaling to ensure visibility across different character scales (e.g., match scale 0.25, lobby scale 0.69).

**Implementation Details:** - Each tentacle consists of six segments that maintain proper spacing and length. - Size values are dynamically adjusted using `transform.lossyScale.x` for consistent appearance at varying scales.

#### Tilt Control Enhancements (2026-04-04)

**Objective:** - Complete integration of tilt control in various UI elements and settings, addressing gaps from a previous session.

**Key Changes:**

1. **Tilt in Rebind UIs:** - Added "Tilt" to the rebindable actions in `InputRebindMenuUI` and `PlayerSettingsPanel`. - Updated `InputRebindUIController` for controller presets, ensuring correct bindings across different control schemes.

2. **Auto-Tilt Toggle Wiring:** - Confirmed existing wiring via `PlayerMenuSetupWizard`, ensuring functionality is intact. - The toggle is accessible only to controller players and adjusts based on their input mode (visible in the settings panel accessed through menu).

3. **Per-Player Menu Hint Text:** - Introduced a new UI prompt system to guide players on accessing settings during a match. - Updated `PlayerMenuController` to dynamically display hint text based on game state and player device. - Enhanced `PlayerSettingsPanel` with instruction text for closing settings.

**Editor Wiring:** - Utilized `MenuHintWiringWizard` to automate the setup of menu hint texts in both the PlayerPrefab and LobbyScene, ensuring consistent UI elements across scenes.

### Architectural Insights

- **Tentacle Implementation:** Ensures visual consistency by auto-scaling and maintaining visibility across different game modes. - **Tilt Control Integration:** Provides a seamless experience for players using controllers, with clear guidance through dynamic hint texts. - **UI Consistency:** Automated wiring scripts ensure that UI elements are correctly set up and maintained, reducing manual errors.

These updates collectively enhance the player experience by refining visual effects and improving control accessibility, ensuring a smoother gameplay interaction across various modes and devices.

Raw session notes

2026-03-29 — SFX Options for All Assigned Slots

Summary

Added alternative option files for every currently-assigned SFX slot so the user can audition and swap without losing the existing working sounds.

AUDIO.md

Added ## SFX Options — Assigned Slots (Alternatives) section to playinstigator_docs/Games/Ritual & Ruin/AUDIO.md with per-slot tables listing all downloaded files and Freesound URLs.

---

Next Steps

  • Open Unity, navigate to Assets/ProtoV2/Audio/SFX/Options/ in Project window
  • Audition each folder to find preferred alternatives
  • Copy chosen file to correct SFX/ subfolder, rename, re-assign in Inspector

---

2026-03-29 — Audio Variation System + SFX Options

Summary

Built centralized audio variation system and sourced replacement options for all unassigned SFX slots.

---

AudioVariation System

Created Assets/ProtoV2/Scripts/Audio/AudioVariation.cs — a single [Serializable] class embedded in every audio script as one Inspector foldout. Replaces the scattered per-script variation fields that were being duplicated.

Parameters: pitch ±, volume ±, startTime (random clip offset), low-pass cutoff ±, reverb ±

Methods:

  • Init(go) — called in Awake, caches AudioLowPassFilter / AudioReverbFilter if present
  • PlayOneShot(source, clip, vol) — variation + PlayOneShot
  • PlayLoop(source, clip, vol) — variation + pitch/filter + Play() with loop=true
  • PlayFromOffset(source, clip, vol) — variation + startTime + Play() non-looping (throttled impacts)

All 6 audio scripts updated to use it: AudioManager, PlayerAudioSource, AltarAudioSource, BloodEmitterAudioSource (two instances — loop + burst), FloorImpactAudio, UIAudioManager.

Bug fixed: FloorImpactAudio.SharedImpactClip was never being set at runtime — floor blood impacts were silent. Fixed by adding bloodImpactClip field to AudioManager and wiring the static in Awake(). Requires assigning blood_impact.ogg to the new "Floor Impact" field on the AudioManager GO in Inspector.

---

SFX Audit

Checked which audio slots are actually assigned vs. missing. Found that several "removed" clips still have files on disk but are unassigned in the Inspector:

| Slot | File | Status |

|---|---|---|

| deathClip | SFX/Character/player_death.ogg | Exists but unsuitable |

| formTransformClip | SFX/Character/form_transform.ogg | Exists but unsuitable |

| flowLoopClip | SFX/Fluid/emitter_flow.ogg | Exists but unsuitable |

| All 5 UIAudioManager clips | SFX/UI/*.ogg | Exist but unsuitable |

---

SFX Options Downloaded

Downloaded and organised replacement options to Assets/ProtoV2/Audio/SFX/Options/:

| Folder | Files | Sources |

|---|---|---|

| Options/Death/ | 9 | Kenney Sci-Fi Sounds (slime, explosionCrunch), Kenney Impact Sounds (impactSoft_heavy) |

| Options/Transform/ | 8 | Kenney Sci-Fi Sounds (forceField x5, computerNoise x2) |

| Options/FlowLoop/ | 9 | Kenney Sci-Fi Sounds (spaceEngineLow, engineCircular, spaceEngineSmall) |

| Options/UI_Click/ | 11 | Kenney Interface Sounds (click x5), Kenney UI Audio (click x5, mouseclick) |

| Options/UI_Hover/ | 12 | Kenney UI Audio (rollover x6), Kenney Interface Sounds (scroll x4, tick x2) |

| Options/UI_Pause/ | 15 | Kenney Interface Sounds (toggle x4, switch x7, open x2, close x2) |

| Options/UI_PlayerJoin/ | 7 | Kenney Interface Sounds (confirmation x4, bong, pluck x2) |

| Options/UI_Transition/ | 9 | Kenney Interface Sounds (maximize x5, glitch x4) |

All Kenney files are CC0. Total: 80 auditionable options across 8 slots.

Additional Freesound CC0 and Pixabay options (40+ more) documented in AUDIO.md with direct URLs for manual download (Freesound requires login).

---

AUDIO.md Updated

playinstigator_docs/Games/Ritual & Ruin/AUDIO.md updated with:

  • Corrected slot statuses (file-exists-but-unsuitable vs. truly unassigned)
  • Full Options section per slot — downloaded files + manual-download Freesound/Pixabay links
  • How-to-assign workflow
  • Credits table updated with Kenney Sci-Fi Sounds and Impact Sounds

---

Next Steps

  • Audition Options files in Unity, pick one per slot, rename and assign in Inspector
  • Assign blood_impact.ogg to AudioManager GO's "Floor Impact" field (fix for silent floor impacts)
  • For slots where no Kenney option feels right: download preferred Freesound/Pixabay option using the URLs in AUDIO.md

---

Evolution Size Fix + Asset Catalog + Resolved Issue Cleanup (2026-03-29)

---

1. Terminal Evolution Size Fix

Issue

Terminal evolution (Tier 2) scale multiplier of 1.5× made the jellyfish too large to pass through floor holes.

Fix

File: Assets/ProtoV2/Scripts/JellyfishVisuals.cs — line 194

Scale progression (final):

| Tier | Scale | Visual cues |

|---|---|---|

| Base (0) | 1.0× | White tint, squish 0.20 |

| Enhanced (1) | 1.2× | Blue tint, squish 0.28 |

| Terminal (2) | 1.3× | Gold tint, squish 0.38, pulse 0.75× faster |

Terminal is still visually distinct (gold color, faster pulse, more squish, slightly larger) but now fits through floor holes.

---

2. Resolved Issues Cleanup

Removed the following items from the open blockers list — all confirmed resolved:

  • TQ-4 (Pause + ObiFluid): ObiFluid particles correctly pause/resume with Time.timeScale = 0. No code changes needed.
  • maxSurfaceChunks: Already reduced to 2000 in ObiSolver Inspector. No further action needed.
  • ObiNativeList finalizer: Mitigated by D3D11 (GPU buffers handled more gracefully). Lower priority, not blocking.

Open blockers: None.

---

3. Asset Catalog

Created Confirmed/strategy/Asset Catalog.md — full inventory of 38 purchased Unity assets with applicability ratings for Ritual & Ruin.

Summary:

  • Tier 1 (use these): Feel, Amplify Shader Editor, Amplify Shader Pack, Obi Rope, Obi Softbody, Motion Blur, Magic Effects FREE, DOTween
  • Tier 2 (potentially useful): Elemental Spells VFX, Easy Save, KayKit Platformer Pack, Weather Maker, Fantasy RPG GUI, Obi Cloth
  • Tier 3 (not relevant): Humanoid character packs, city environments, turret assets, tank, fruit market, 2D tools

Priority integration order: Feel → Obi Rope → Motion Blur → Magic Effects FREE → Easy Save → Amplify Shader Editor

---

4. Evolution Visual Distinction — Next Steps

Current visual distinction between tiers (post-fix):

  • Size: 1.0 → 1.2 → 1.3 (subtle)
  • Color: white → blue → gold ✅ clear
  • Squish: 0.20 → 0.28 → 0.38 (subtle)
  • Pulse rate: unchanged → unchanged → 0.75× faster ✅ readable

Suggested enhancements using owned assets:

  • Feel: Add camera shake burst + flash on evolution event (OnEvolution subscriber)
  • Magic Effects FREE: Spawn a VFX burst prefab at player position on evolution
  • Obi Rope: Upgrade tentacles to physical simulation (more dramatic swing on evolution)
  • Amplify Shader Editor: Add emissive glow increase per tier to jellyfish bell material

Design discussion needed on which to prioritize.

---

Jellyfish Squish + Emitter Indicator + Scroll Camera Fix (2026-03-29)

---

1. Jellyfish Cap Horizontal Squish

Issue

XZ bulge on pulse was 30% of squish amount — too subtle.

Fix

File: Assets/ProtoV2/Scripts/JellyfishVisuals.cs

XZ bulge doubled — bell now visibly widens on each pulse. Spring recovery unchanged.

---

2. Emitter Indicator — No Longer Lands on Players

Issue

BloodEmitterIndicator.DrawCircle() used Physics.Raycast with no layer filter. When a player flew between the emitter and the floor, the raycast hit the player's collider, placing the circle on top of the player.

Root Cause

All objects (players, floors, walls) are on the Default layer — no layer separation to filter against.

Fix

File: Assets/ProtoV2/Scripts/BloodSystem/BloodEmitterIndicator.cs

Replaced physics raycast entirely with chunk-coordinate lookup. We already know the grid layout — no need to cast rays:

Walks top→middle→bottom floor. Finds first floor that has a solid chunk at the emitter's XZ grid position. Surface Y derived from floor root + ChunkHeight/2. Removed raycastLength field entirely.

---

3. Camera Scroll — Single Source of Truth for Floor Spacing

Issue

ScrollingFloorCamera had its own [SerializeField] float floorVerticalSpacing = 5f independent from FloorManager.floorVerticalSpacing. If the two values differed in the Inspector, the camera would scroll to the wrong Y position.

Fix

File: Assets/ProtoV2/Scripts/CameraSystem/ScrollingFloorCamera.csCalculateFloorYPosition()

Camera's own floorVerticalSpacing field is now only a fallback for editor gizmos. At runtime, both floor generation and camera scroll use the same value from FloorManager.

Current values (defaults)

  • FloorManager.floorVerticalSpacing = 5f — adjust this to change floor spacing
  • ScrollingFloorCamera.scrollDuration = 2.5f — how long the scroll animation takes

---

4. Ritual Completion VFX — AltarVFXController

Status

Feel (More Mountains) and Magic Effects FREE (Hovl Studio) are now imported.

New File

Assets/ProtoV2/Scripts/BloodSystem/AltarVFXController.cs — attach alongside AltarParticleConsumer on the Altar prefab.

Drives:

  • OnMeterFull → spawn looping countdown VFX (e.g. Magic circle.prefab or Healing circle.prefab)
  • OnRitualComplete → destroy countdown VFX + spawn completion burst (e.g. Red energy explosion.prefab) + play MMF_Player feedbacks (screen shake, flash, audio)
  • OnAltarReset → destroy countdown VFX

Inspector Wiring Required (on Altar prefab)

1. Add AltarVFXController component

2. Create child GO with MMF_Player, add Screen Shake + Camera Flash + Audio feedbacks → assign to completionFeedbacks

3. Assign completionVFXPrefab = Red energy explosion.prefab (Hovl Studio Magic Effects)

4. Assign countdownVFXPrefab = Magic circle.prefab or Healing circle.prefab

5. Tune completionVFXYOffset and countdownVFXYOffset to sit correctly at altar ground level

Also Available

  • Elemental Spells Full Pack VFX — still in .unitypackage archive, not yet imported. Contains additional burst/explosion options for ritual completion.

---

5. Lobby Scene — Tentacle Scale Fix

Issue

JellyfishVisuals tentacle rim positions were set in world-space Inspector values. characterScale in LobbyScene was 1.0 while MatchScene uses 0.25. At scale 1, the bell is 4× larger than the rim radius (0.4), so tentacles appeared hidden inside the player body.

Fix — Two Parts

Part A: LobbyScene.unityMultiplayerManager.characterScale changed 10.25 so all scenes use the same scale.

Part B: JellyfishVisuals.csBuildTentacles() and UpdateTentacles() now multiply all spatial values by transform.lossyScale.x (ws):

Inspector values are now local-space (as if characterScale = 1). ws scales them to world-space at runtime, so tentacles are always proportionally correct regardless of characterScale.

Part C: PlayerPrefab.prefab Inspector values converted from old world-space to new local-space (÷ 0.25 = × 4):

| Field | Before (world-space) | After (local-space) |

|---|---|---|

| rimRadius | 0.4 | 1.6 |

| rimY | 0.56 | 2.24 |

| segmentLength | 0.15 | 0.6 |

| startWidth | 0.15 | 0.6 |

| endWidth | 0.04 | 0.16 |

At characterScale = 0.25, ws = 0.25, so effective world-space values are identical to before.

---

Dev Log — 2026-03-29 — MMFeedbacks Integration

Summary

Full MMFeedbacks (Feel) integration across all major gameplay events in Ritual & Ruin. Every significant player action, match event, and altar moment now triggers camera shake and/or freeze-frame feedback.

---

What Was Done

AltarVFXController — Namespace Fix

  • File: Assets/ProtoV2/Scripts/BloodSystem/AltarVFXController.cs
  • Added using CupFairy.BloodSystem; to fix CS0246 compile error (AltarParticleConsumer not found)
  • Controller was already written; this unblocked it from compiling

Script Changes — MMF_Player Fields Added

All scripts received using MoreMountains.Feedbacks; + a [SerializeField] private MMF_Player field + a ?.PlayFeedbacks(transform.position) call at the relevant event moment:

| Script | Field | Trigger |

|---|---|---|

| DeathHandler.cs | deathFeedback | Before OnDied?.Invoke() |

| UnifiedBar.cs | evolutionFeedback | Before OnEvolution?.Invoke() in TriggerEvolution() |

| CharacterController1.cs | pulseFeedback | After PulsedThisFrame = true in pulse timer |

| TransformController.cs | formToggleFeedback | At start of Toggle() |

| MatchCountdown.cs | countdownBeatFeedback, goFeedback | Each countdown beat tick; GO! display |

| MatchManager.cs | teamEliminatedFeedback, matchEndFeedback | RecordTeamEliminated(); EndMatch() |

PlayerPrefab — 4 Feedback Child GOs

Added 4 new child GameObjects under the PlayerPrefab root, each with a Transform + MMF_Player component:

| GO Name | fileID | Wired to Script Field | Feedbacks |

|---|---|---|---|

| DeathFeedbacks | &1111111111111111103 | DeathHandler.deathFeedback | FreezeFrame 0.08s + PositionShake 0.4s/range 0.5 |

| EvolutionFeedbacks | &1111111111111111106 | UnifiedBar.evolutionFeedback | FreezeFrame 0.05s + PositionShake 0.3s/range 0.3 |

| PulseFeedbacks | &1111111111111111109 | CharacterController1.pulseFeedback | PositionShake 0.1s/range 0.05 (subtle — fires every 0.35s) |

| FormToggleFeedbacks | &1111111111111111112 | TransformController.formToggleFeedback | PositionShake 0.2s/range 0.15 |

All 4 Transform fileIDs (1111111111111111102, 105, 108, 111) added to root Transform m_Children list (fileID 5020478598309948295).

MatchScene — 4 Feedback Child GOs + Scene Components

Previously added (pre-this-session):

  • MMPositionShaker on Main Camera
  • MMTimeManager GO in scene
  • 4 feedback child GOs under MatchCountdown and MatchManager

This session — populated the MMF_Player feedbacks in each GO:

| GO Name | fileID | Wired to Script Field | Feedbacks |

|---|---|---|---|

| CountdownBeatFeedbacks | &1482767222 | MatchCountdown.countdownBeatFeedback | PositionShake 0.1s/range 0.1 |

| GoFeedbacks | &872176054 | MatchCountdown.goFeedback | FreezeFrame 0.05s + PositionShake 0.3s/range 0.3 |

| TeamEliminatedFeedbacks | &247481994 | MatchManager.teamEliminatedFeedback | FreezeFrame 0.06s + PositionShake 0.35s/range 0.4 |

| MatchEndFeedbacks | &1200068797 | MatchManager.matchEndFeedback | FreezeFrame 0.1s + PositionShake 0.5s/range 0.5 |

Altar Prefab — Completion Feedbacks (Previous Session)

Already done: CompletionFeedbacks child MMF_Player has:

  • MMF_Light (existing)
  • MMF_FreezeFrame (0.05s)
  • MMF_PositionShake (0.3s/range 0.3, random XY)

---

Feedback Intensity Rationale

| Event | Intensity | Reasoning |

|---|---|---|

| Match end | Strongest (0.1s freeze, 0.5 shake) | Game-ending moment, maximum impact |

| Player death | Strong (0.08s freeze, 0.5 shake) | High-stakes moment |

| Team eliminated | Medium-strong (0.06s freeze, 0.4 shake) | Significant match event |

| GO! start | Medium (0.05s freeze, 0.3 shake) | Match start signal |

| Evolution | Medium (0.05s freeze, 0.3 shake) | Power-up moment |

| Altar completion | Medium (0.05s freeze, 0.3 shake) | Ritual payoff |

| Form toggle | Subtle (0.2s shake/0.15 range) | Frequent action, shouldn't fatigue |

| Countdown beat | Subtle (0.1s shake/0.1 range) | Rhythmic, very frequent |

| Staccato pulse | Minimal (0.1s shake/0.05 range) | Fires every 0.35s, must be imperceptible individually |

---

Technical Notes

  • All feedbacks use managed reference YAML format (FeedbacksList: + references: RefIds:) required by MMF_Player v3
  • Scene MMF_Players use a hybrid format (Feedbacks: [] legacy field + FeedbacksList: + references:) — both must be present
  • MMTimeManager GO required in scene for FreezeFrame to function (timescale manipulation)
  • MMPositionShaker on Camera required to receive PositionShake events (channel 0)
  • Owner field in each feedback data points to the MMF_Player component's own fileID
  • All PositionShake feedbacks: RandomizeDirectionX: 1, RandomizeDirectionY: 1, RandomizeDirectionZ: 0 (2D shake only, no depth)

---

Files Modified

  • Assets/ProtoV2/Scripts/BloodSystem/AltarVFXController.cs
  • Assets/ProtoV2/Scripts/DeathHandler.cs
  • Assets/ProtoV2/Scripts/UnifiedBar.cs
  • Assets/ProtoV2/Scripts/CharacterController1.cs
  • Assets/ProtoV2/Scripts/TransformController.cs
  • Assets/ProtoV2/Scripts/MatchCountdown.cs
  • Assets/ProtoV2/Scripts/MatchManager.cs
  • Assets/ProtoV2/Prefabs/PlayerPrefab.prefab (4 new GO+Transform+MMF_Player blocks)
  • Assets/ProtoV2/Scenes/MatchScene.unity (4 MMF_Player feedbacks populated)

---

Obi Fluid Memory Leak — Continued Investigation (2026-03-29)

Continued from 2026-03-28_ObiFluid_MemoryLeak_Fix.md. Previous session patched the rendering-side leak (VolumePass materials, MaterialPropertyBlock, AsyncGPUReadback closures). This session found and addressed the simulation-side leak.

---

Monitoring Setup

PowerShell script at C:/Users/ReconUnPro/AppData/Local/Temp/monitor_mem2.ps1 — polls every 5s:

  • Game process working set (MB) via Get-Process
  • System RAM available / used / % via Get-CimInstance Win32_OperatingSystem

---

Isolation Test Results

Build #006 — All Obi Rendering Disabled

Disabled three layers to fully strip rendering:

1. Removed ObiFluidRendererFeature from m_RendererFeatures in PC_Renderer.asset

2. ObiParticleRendererm_Enabled: 0 on BloodEmitter.prefab

3. ObiFluidSurfaceMesherm_Enabled: 0 on BloodEmitter.prefab

Result: Memory still grew at ~161 MB/s. Flush/scene reload gave no relief.

Conclusion: Leak exists on BOTH sides:

  • Rendering side: ~320 MB/s (patched in prior session, confirmed by rate drop)
  • Simulation side: ~161 MB/s (still present — this session's target)

---

Patches Applied This Session

Patch 3 — `ObiSolver.PushActiveParticles()` (previously applied)

Pre-allocate activeParticles to allocParticleCount before clear so EnsureCapacity never fires mid-emission.

Patch 4 — `ComputeSolverImpl.SetActiveParticles()` (previously applied)

Guard: only call AsComputeBuffer if buffer is null. Otherwise UploadFullCapacity().

Patch 5 — `ObiSolver.PushSimplices()`

File: Assets/Obi/Scripts/Common/Solver/ObiSolver.cs

Added before simplices.Clear():

Why: dirtySimplices is set every time a particle is activated (every emission frame). Without pre-allocation, EnsureCapacity fires repeatedly as the active count grows, each time doubling capacity and nulling m_ComputeBuffer.

Patch 6 — `ComputeSolverImpl.SetSimplices()`

File: Assets/Obi/Scripts/Common/Backends/Compute/Solver/ComputeSolverImpl.cs

Replaced unconditional AsComputeBuffer calls on simplices (line 515) and cellCoords (line 516):

Result of builds #006-#007: Memory still grew at ~161 MB/s. These patches were correct but insufficient — the ROOT CAUSE was elsewhere.

---

Root Cause Found — Bloated Blueprint Capacity

The Discovery

Deep Obi source analysis by Opus architect agent identified the actual culprit.

BloodFluid.asset had capacity: 20000 per emitter.

With 9 emitters active (3 emitters/floor × 3 floors), the solver pre-allocates:

  • Total particle slots: 9 × 20,000 = 180,000
  • positions.count = 180,000 (used by SetCapacity)

This caused ComputeParticleGrid.SetCapacity() to create:

| Buffer | Size |

|---|---|

| neighbors (180k × 2 × 128 neighbors × 4B) | 184 MB |

| All 20+ grid buffers total | ~230 MB |

| colliderContacts readback buffer | ~46 MB |

| 26 particle arrays in ParticleCountChanged | ~170 MB |

~650 MB allocated at match start — before a single particle is emitted.

The D3D12 driver pools these on disposal (never returns to OS), so repeated SetCapacity calls accumulate permanently. colliderContacts.Readback() in ObiSolver.RequestReadback() fires every frame (whenever OnCollision != null, which our scripts always satisfy), staging a ~46 MB readback buffer per frame into D3D12's READBACK heap.

Why 20,000 Was Wrong

Our ObiEmitter runs in burst mode:

  • 7 particles/sec × 1.5s burst every 5s = ~2.1 particles/sec average
  • Particle lifespan: 60s
  • Peak live particles per emitter: 2.1 × 60 = ~126

20,000 capacity = 158× overprovision. The default Obi blueprint value was never adjusted for our actual usage.

The Fix

Assets/ProtoV2/Blueprints/BloodFluid.asset line 1559:

200 gives 1.6× headroom over peak (126 live particles). With 9 emitters:

  • Total particle slots: 9 × 200 = 1,800 (vs 180,000)
  • neighbors buffer: 1.8 MB (vs 184 MB) — 100× reduction
  • colliderContacts readback: ~460 KB (vs 46 MB) — 100× reduction

---

Secondary Root Cause — ObiNativeList Finalizer (Scene Reload Doesn't Help)

ObiNativeList.~ObiNativeList() calls DisposeOfComputeBuffer() from the GC finalizer thread. GraphicsBuffer.Dispose() must run on the main thread — called from the wrong thread, it silently fails. GPU buffers are permanently orphaned and survive scene reload.

This explains why Flush/scene restart never released memory — not a leak per se but a one-time orphan per solver lifetime.

Status: Not patched yet. Lower priority now that capacity is fixed.

---

Build Log

| Build | Change | Sim Leak Rate | Notes |

|---|---|---|---|

| #004 (prev) | Rendering patches | ~481 MB/s | Rendering still enabled |

| #005 | m_Active: 0 attempt | ~481 MB/s | Wrong YAML field — fluid still rendered |

| #006 | All rendering disabled | ~161 MB/s | Confirmed sim-side leak |

| #007 | SetSimplices patch | ~161 MB/s | Patch correct but not root cause |

| #008 | capacity: 200 + SetSimplices | ~134 MB/s | Baseline polluted (monitoring started mid-match at 20 GB); capacity patches correct but D3D12 root cause not yet identified |

| #009 | ObiSolver disabled | ~135 MB/s (polluted) / flat after reboot | Confirmed leak stops when match ends even without Obi; polluted D3D12 baseline made rate unreliable |

| #010 | ObiSolver disabled + D3D11 | ~0 MB/s | Flat 1280 MB throughout full match — confirmed D3D12 deferred-release pool is root cause |

| #011 | Full Obi re-enabled + D3D11 | ~0.1 MB/s | FIXED — 1337→1347 MB over 90s. Stable. |

---

Root Cause (Final)

The leak was D3D12-specific deferred-release pool behaviour, not a true memory leak in code.

  • D3D12: GraphicsBuffer.Dispose() queues the buffer for deferred release. The driver returns memory to an internal pool, NOT to the OS. New allocations commit fresh OS pages instead of reusing pooled ones. Pool grows monotonically for the lifetime of the process.
  • D3D11: Disposed resources are returned to the driver pool immediately and reused for new allocations. Working set stays flat.

Obi's emit/die particle lifecycle creates a small but steady stream of new GraphicsBuffer allocations per frame (SetSimplices, readbacks). On D3D11 these are reused. On D3D12 they permanently expand the pool.

Fix applied: ProjectSettings.asset — Standalone Windows graphics API forced to Direct3D11 (m_APIs: 02000000, m_Automatic: 0).

---

What Didn't Work / Lessons

  • m_Enabled: 0 on a URP ScriptableRendererFeature — WRONG field. URP checks m_Active, not m_Enabled.
  • Removing ObiFluidRendererFeature from m_RendererFeatures — correct way to disable URP features in YAML.
  • SetSimplices/SetActiveParticles guards — correct patches but not the root cause. The leak was D3D12 pool behaviour, not allocation frequency or size.
  • Capacity 20000→200 — correct and reduces one-time static allocation by 100×, but doesn't fix D3D12 pool growth.
  • Polluted D3D12 baseline (no reboot between test builds) made rate measurements unreliable across builds. Always reboot when comparing rates.
  • Source-only analysis hit limits — binary isolation (disable ObiSolver, then switch API) was faster than reading code.

---

Remaining Items

  • [x] Re-enable rendering (ObiFluidRendererFeature, ObiParticleRenderer, ObiFluidSurfaceMesher)
  • [x] Verify memory is stable with rendering re-enabled — CONFIRMED 0.1 MB/s
  • [x] Force D3D11 for all Standalone builds — ProjectSettings.asset permanently set (m_APIs: 02000000, m_Automatic: 0)
  • [ ] Fix ObiNativeList finalizer thread issue (GPU buffer orphaning on scene reload) — lower priority, one-time per session; mitigated by D3D11
  • [ ] Update maxSurfaceChunks if needed now that particle count is much smaller

---

Status: RESOLVED (2026-03-29)

Memory leak investigation is complete. Game is stable at ~0.1 MB/s (effectively flat). All Standalone builds will use Direct3D11 permanently for as long as Obi Fluid is in the project. Proceeding to next development phase.

---

2026-03-31 — Centralised Audio Management Panel

Summary

Added a GameAudioData ScriptableObject as a single source of truth for all 22 audio clip slots across the game. Includes a rich custom Inspector panel with category foldouts, clip-count badge, preview buttons, and auto-link tools.

---

Problem

Audio clips were scattered across 5 different components on 4 different prefabs/GOs:

  • AudioManager (MatchScene GO) — 9 clips
  • PlayerAudioSource (PlayerPrefab) — 5 clips
  • AltarAudioSource (Altar prefab) — 3 clips
  • BloodEmitterAudioSource (BloodEmitter prefab) — 2 clips
  • UIAudioManager (DontDestroyOnLoad GO) — 5 clips

To reassign a clip you had to hunt through 4 prefabs. No way to see the full audio inventory at a glance.

---

Solution

New Files

Assets/ProtoV2/Scripts/Audio/GameAudioData.csScriptableObject holding all 22 AudioClip fields + sfxGroup / uiGroup AudioMixerGroup refs, grouped by category:

  • Match/Countdown, Floor Scroll, Match Outcome, Outlast Ticker, Floor Impact
  • Player, Altar, Blood Emitter, UI

Assets/ProtoV2/Scripts/Editor/GameAudioDataEditor.cs — Custom Inspector with:

  • Bold header "Ritual & Ruin — Audio Data"
  • HelpBox badge: "X / 22 clips assigned" (warning if incomplete)
  • Per-category colour-coded foldouts (persisted via EditorPrefs) each showing assigned/total
  • Every slot: object field + preview button (AudioUtil reflection, fallback safe)
  • "Find & Link Clips from SFX Folder" button — scans Assets/ProtoV2/Audio/SFX/ and auto-assigns by filename pattern matching
  • "Find & Link Mixer Groups" button — loads GameAudioMixer from Resources and assigns SFX + UI groups

Assets/ProtoV2/Scripts/Editor/GameAudioDataWizard.cs — Menu items under Tools/Ritual & Ruin/Audio/:

  • Create GameAudioData Asset — creates Assets/ProtoV2/Audio/GameAudioData.asset, selects + pings it
  • Auto-Link Clips & Mixers — one-shot auto-fills the asset from the SFX folder + mixer, logs all assignments

Modified Files (backward-compatible)

Each of the 5 audio components gained:

1. [Header("Audio Data")] [SerializeField] private GameAudioData _audioData; as the first serialized field

2. An if-block at the top of Awake() that copies clips from _audioData into the existing private fields when assigned

Components affected:

  • AudioManager.cs — 9 clips + sfxGroup
  • PlayerAudioSource.cs — 5 clips + sfxGroup
  • AltarAudioSource.cs — 3 clips + sfxGroup
  • BloodEmitterAudioSource.cs — 2 clips + sfxGroup
  • UIAudioManager.cs — 5 clips + uiGroup

Backward compatible: if _audioData is null, all individual Inspector-assigned clips continue to work unchanged.

---

How to Set Up

1. Tools/Ritual & Ruin/Audio/Create GameAudioData Asset — creates the asset

2. Select it in Project → Inspector shows the full panel

3. Click "Find & Link Clips from SFX Folder" + "Find & Link Mixer Groups" to auto-fill

4. Assign the asset to the _audioData slot on: AudioManager (MatchScene), UIAudioManager (MainMenu scene), PlayerPrefab, Altar prefab, BloodEmitter prefab

Or use Tools/Ritual & Ruin/Audio/Auto-Link Clips & Mixers to do steps 2+3 from a menu item.

---

Files Changed

| File | Change |

|---|---|

| Assets/ProtoV2/Scripts/Audio/GameAudioData.cs | NEW — ScriptableObject, 22 clips + 2 mixer groups |

| Assets/ProtoV2/Scripts/Editor/GameAudioDataEditor.cs | NEW — Custom Inspector panel |

| Assets/ProtoV2/Scripts/Editor/GameAudioDataWizard.cs | NEW — Create + auto-link menu items |

| Assets/ProtoV2/Scripts/Audio/AudioManager.cs | +_audioData field + Awake copy block |

| Assets/ProtoV2/Scripts/Audio/PlayerAudioSource.cs | +_audioData field + Awake copy block |

| Assets/ProtoV2/Scripts/Audio/AltarAudioSource.cs | +_audioData field + Awake copy block |

| Assets/ProtoV2/Scripts/Audio/BloodEmitterAudioSource.cs | +_audioData field + Awake copy block |

| Assets/ProtoV2/Scripts/Audio/UIAudioManager.cs | +_audioData field + Awake copy block |

---

2026-03-31 — Obi Rope + Obi Softbody Jellyfish Upgrade

What Was Done

Replaced the procedural LineRenderer spring-chain tentacles with proper Obi Rope physics tentacles, and added an Obi Softbody jelly core system. Both packages (Obi Rope 7.x, Obi Softbody 7.x) were already imported by the developer.

---

Files Changed

`Assets/ProtoV2/Scripts/JellyfishVisuals.cs` — Full rewrite (tentacle system only)

Removed:

  • All LineRenderer fields: _tentacles, _tentacleMaterials, _segmentPositions, _rimOffsetsLocal
  • Inspector params: segmentCount, segmentLength, dampFactor, startWidth, endWidth, swayAmplitude, swayFrequency
  • Methods: BuildTentacles(), UpdateTentacles(), BuildTentacleMaterial()

Added:

  • [SerializeField] ObiSolver _solver — auto-found via FindObjectOfType if null; tentacles skipped gracefully if solver unavailable (bell squish + hover bob still work)
  • Inspector params: ropeLength=0.9, ropeVisualThickness=0.05, ropeBendCompliance=0.01, ropeParticleCount=8
  • Runtime arrays: _ropes, _blueprints, _anchorTransforms, _ropeMaterials
  • BuildRimOffsets() — same XZ rim circle math as before
  • BuildObiRopeTentacles() — creates 6 ObiRope actors at Start(), each parented under ObiSolver
  • UpdateAnchorTransforms() — updates anchor Transform world positions each frame (same formula as old anchor calc: world Y from VisualRoot + rim XZ from root direction)
  • CreateRopeMaterial() — URP/Unlit transparent material, team-colored

Unchanged: UpdateHoverBob(), UpdateBellSquish(), ApplyEvolutionTint(), SetTentacleColor(), all evolution/color fields, OnDestroy() cleanup pattern.

Rope setup per tentacle:

1. Anchor Transform child of root GO (NOT VisualRoot — avoids inheriting bell tilt)

2. ObiRopeBlueprint: 2 control points (root at 0,0,0 → tip at 0,-length,0), tapered thickness (tip = 40% of root), root color opaque → tip alpha=0

3. ObiRope + ObiPathSmoother + ObiRopeExtrudedRenderer (DefaultRopeSection)

4. ObiParticleAttachment.Static on groups[0] → anchor transform

5. Parent under solver LAST (triggers AddToSolver)

---

`Assets/ProtoV2/Scripts/JellyfishSoftCore.cs` — New

ObiSoftbody "jelly interior" component. Add to PlayerPrefab root. Requires a pre-baked blueprint (run wizard below).

At Start(): creates a sphere-shaped ObiSoftbody GO under the solver, dynamically pin-attached to the character root via ObiParticleAttachment.Dynamic with configurable _compliance (default 0.002 — elastic spring lag). Rendered with a translucent blue sphere.

Exposes:

  • PulseSquish(float inwardSpeed) — applies inward radial velocity to all particles on pulse
  • SetColor(Color) — evolution tinting
  • IsReady — safe call guard

---

`Assets/ProtoV2/Scripts/Editor/JellyfishSoftCoreSetupWizard.cs` — New

Menu: Ritual & Ruin / Setup Jellyfish Soft Core

One-click editor wizard that:

1. Gets Unity's built-in sphere mesh via GameObject.CreatePrimitive(PrimitiveType.Sphere)

2. Creates ObiSoftbodyBlueprint, assigns mesh, calls GenerateImmediate()

3. Saves blueprint to Assets/ProtoV2/SoftbodyBlueprints/JellyfishCoreSoftbody.asset

4. Opens PlayerPrefab via EditPrefabContentsScope, adds JellyfishSoftCore to root, wires blueprint

---

How to Use After Compilation

Obi Rope Tentacles (automatic)

  • Assign ObiSolver reference on JellyfishVisuals in PlayerPrefab Inspector, OR leave null (auto-found at runtime)
  • Enter Play mode → 6 ObiRope tentacles simulate under the solver, physically trailing behind character movement

Obi Softbody Jelly Core (opt-in)

1. Wait for scripts to compile

2. Run Ritual & Ruin / Setup Jellyfish Soft Core from the Unity menu

3. Enter Play mode → translucent sphere lags behind character with elastic follow

Tuning parameters (JellyfishSoftCore Inspector):

  • _compliance: 0.001 (tight) → 0.01 (very sloshy)
  • _coreOffset: default (0, 1.5, 0) — positions core inside bell
  • _coreScale: default 0.35 — sphere radius
  • _coreColor: default translucent blue-white (0.5, 0.85, 1, 0.25)

Tuning parameters (JellyfishVisuals Inspector):

  • ropeLength: 0.9 — tentacle length
  • ropeVisualThickness: 0.05 — cross-section radius
  • ropeBendCompliance: 0.01 — stiffness (0=rigid, 0.1=very floppy)
  • ropeParticleCount: 8 — simulation resolution

---

Architecture Notes

  • Rope GOs are parented under ObiSolver, NOT under the character. Anchor Transforms (children of character root) follow the character each frame; ObiParticleAttachment.Static pins rope particle 0 to each anchor.
  • Anchor Transforms are children of the root (not VisualRoot) so they don't inherit pour tilt or bell squish rotation.
  • OnDestroy() cleans up all rope GOs, blueprint ScriptableObjects (DestroyImmediate), materials, and anchor GOs — no leaks on respawn/rematch.
  • If ObiSolver not found at Start, bell squish and hover bob still work — only tentacles are skipped.
  • JellyfishSoftCore is fully optional. No changes to JellyfishVisuals required for softbody to work.

---

Design Vault Reference

  • Asset Catalog Tier 1: Obi Rope ✅ implemented, Obi Softbody ✅ implemented
  • Jellyfish Visuals confirmed doc: "Future: replace with ObiRopes" ✅ done

---

2026-04-03 — Tentacle Revert: LineRenderer Spring-Chain

What Changed

Reverted jellyfish tentacles from ObiRope back to LineRenderer spring-chain.

Root cause for revert: ObiRope tentacles are physical actors — on every bell pulse the VisualRoot squishes, which moves the ObiParticleAttachment anchor, causing particles to bounce violently. The tentacles should trail softly, not react to the bell pulse physics.

Implementation

JellyfishVisuals.cs — full rewrite

  • 6 LineRenderer tentacles, 6 segments each
  • Spring chain: segment 0 snaps to anchor, segments 1+ lerp toward (prev + Vector3.down * segLen) then distance-constrained to fixed length
  • Auto-scaling: all size values multiplied by transform.lossyScale.x at Start() so tentacles look correct at any character scale (match=0.25, lobby=0.69)
  • Inspector values are scale=1 normalized

Prefab values (scale=1 normalized)

| Field | Value | World value @ scale=0.25 |

|---|---|---|

| rimRadius | 1.3 | 0.325 |

| rimY | 1.5 | 0.375 (anchor above floor) |

| segmentLength | 0.25 | 0.0625/segment, 0.375 total = reaches floor |

| startWidth | 0.6 | 0.15 |

| endWidth | 0.16 | 0.04 |

Key insight on segmentLength: Originally set to 0.6 (= 0.9 world units total at match scale) — tentacles extended 0.525 units below floor, making only ~2 segments visible. Corrected to 0.25 (= 0.375 world units total) so all 6 segments are visible above floor. At lobby scale 0.69: 0.25 × 0.69 × 6 = 1.035 world units = exactly matches anchor height. Both scenes auto-balance.

TentacleSetupWizard.cs

Added "Ritual & Ruin/Cleanup Jellyfish Tentacles (Revert to LineRenderer)" menu item that removes TentacleSolver GO and TentacleAnchor_* from VisualRoot. ObiRope setup code kept shelved for future reference.

Visual Result

Tentacles appear as a radial fringe of colored lines immediately around each jellyfish body. From the isometric top-down camera angle, downward-hanging tentacles are foreshortened — appears as a small colored cluster rather than long dangling appendages. Correctly attached to characters, correctly auto-scaled, correctly team-colored.

---

2026-04-04 — Tilt Control in Rebind UI + In-Match Menu Hints

What Changed

Three pending gaps from the 2026-03-23 manual tilt session completed:

1. Tilt (right stick) added to all rebind UIs

2. Auto-tilt toggle wiring completed (PlayerMenuController + PlayerSettingsPanel)

3. Per-player menu hint text added to in-match HUD and settings panel

---

Tilt in Rebind UIs

Tilt was fully implemented in the input system but never surfaced to players as a rebindable control.

Files changed

InputRebindMenuUI.cs

  • Added "Tilt" to rebindableActions. The UGUI lobby rebind panel now shows a Tilt row for controller players. Keyboard players see no row (Tilt has no keyboard binding — skipped automatically by GetBindingIndexForControlScheme returning -1).

PlayerSettingsPanel.cs

  • Added "Tilt" to rebindableActions. Same behavior in the in-game settings panel.

InputRebindUIController.cs (UI Toolkit lobby panel)

  • BindingNames / ActionMappings: added "tilt" / "Tilt" at index 6
  • ControllerPresetBindings: added tilt as 4th element per preset:
  • L-Stick + Buttons → rightStick
  • L-Stick + Triggers → rightStick
  • R-Stick + Buttons → leftStick (rightStick already used for Move in this preset)
  • D-Pad + Buttons → rightStick
  • ApplyPreset() controller branch: calls ApplyControllerMovePreset(tiltAction, presetPaths[3]) — correct because Tilt is Value/Vector2 (same structure as Move)

---

Auto-Tilt Toggle Wiring

PlayerMenuController and PlayerSettingsPanel code was complete since 2026-03-23. The missing piece was editor wiring. The existing PlayerMenuSetupWizard (Tools > Player Menu Setup) confirmed everything was already in place from a prior run:

Toggle behavior: visible only for controller players (keyboard always uses auto-tilt; hidden for them). Players access it via MenuOpen (Start/Escape) → Settings panel.

---

Per-Player Menu Hint Text

New UI prompt system so players know how to open/close the per-player settings panel during a match.

`PlayerMenuController.cs`

  • Added [SerializeField] TextMeshProUGUI menuHintText
  • Start(): sets initial text, subscribes to PlayerSetup.OnInputModeChanged
  • UpdateHintText() sets text based on current state:

| State | Controller | Keyboard |

|---|---|---|

| Menu closed | START — Settings | ESC — Settings |

| Menu open | START — Close Settings | ESC — Close Settings |

| Waiting for controller | Waiting for controller… | — |

  • Fires on OpenMenu(), CloseMenu(), and any device change event

`PlayerSettingsPanel.cs`

  • Added [SerializeField] TextMeshProUGUI instructionText
  • Set in Show() based on player's input mode:
  • Controller: START or B — Close
  • Keyboard: ESC — Close

Unity wiring — `MenuHintWiringWizard.cs` (new editor script)

Runs via Tools > Wire Menu Hint Text. One-shot, rerunnable.

  • PlayerPrefab: Created MenuHintText TMP child under BarCanvas, anchored below the health bar. Font size 3 (world-space canvas), 65% opacity white, centre-aligned. Wired to PlayerMenuController.menuHintText.
  • LobbyScene / PlayerSettingsPanelCanvas/PanelRoot: Created InstructionText TMP anchored to panel bottom, font size 13, grey. Wired to PlayerSettingsPanel.instructionText.
  • LobbyScene saved.

Players can now manually tilt the jellyfish bowl using the right stick, with the tilt range expanded significantly for more expressive play. Blood is stickier and less likely to spill during movement, and carrier mode slows movement slightly to reduce inertia. These changes make carrying blood feel more deliberate.

The settings system is now fully working. All sliders — volume, resolution, graphics quality, camera shake — are functional in both the main menu and during matches. The SFX volume slider previously had no effect because audio sources weren't wired to the mixer. Fixed. Camera shake toggle now works too.

Several memory leaks were resolved that caused performance to degrade over long sessions. Standalone build crashes were fixed — missing shaders caused visual elements not to spawn in builds at all. Unglamorous sessions, but necessary ones.

Raw session notes

Mar 22 — Memory leaks & outlast phase

Fixed duplicate event subscriptions, mesh asset leaks, static dictionary never cleared between matches, anonymous lambda closures keeping dead objects alive. Added OutlastPanel with 4× time scale drain.

Mar 23 — Tilt system, carrier speed, controller menus

Manual tilt via right stick (75° range). Carrier mode 80% speed. ObiFluid viscosity increased. Per-player lobby settings panel via Start/Escape.

Mar 26 — Build fixes, pause menu

Fixed standalone crashes: missing shaders, GPU memory leak on restart, main menu buttons. Added Settings to Pause and Main Menu.

Mar 27–28 — Full settings system & verification

Audio/video/graphics/gameplay settings, persisted via PlayerPrefs. SFX routing fixed. Camera shake toggle fixed. Resolution default fixed.

Every action in a match now has sound. Countdown ticks, floor scroll, match start and end, player death, form transform, evolution, altar filling, blood hitting the floor, UI buttons — all wired in a single session. The game went from silent to fully voiced in one pass.

Visual feedback improved significantly. Glowing rings appear on floor tiles that have a blood emitter underneath, making it easier to know where blood will come from. Altars now turn red from the bottom up as they fill, so you can read altar progress at a glance without checking a number. Blood emitter fluid was tuned to behave more like blood.

Jellyfish creatures got animated tentacles. Player 4 controller disconnect and reconnect handling was fixed. Blood leaking through floors due to collider issues was resolved.

Raw session notes

Mar 15–16 — Visuals & controller fix

Emitter indicator glowing rings on tile surfaces. Jellyfish animated tentacles. Pouring mechanic first visual pass. Player 4 controller hotplug fix.

Mar 18–19 — Blood & altar

Blood emitter fluid tuning. Altar fill visual shader (URP HLSL, red fill bottom-up). Obi fluid collider leak fix.

Mar 20 — Audio system

Full audio pass: 7 components, 18+ events covered. Countdown, scroll alarm, evolution sting, death sound, form swap, altar sounds, floor impact, UI audio.

Mar 21 — Player 4 controller

Controller disconnect/reconnect handling fixed for Player 4.

All 13 core game systems were completed and wired up in Unity — the game could run a full match end-to-end for the first time. Spawn system, countdown, match flow, win/loss detection, onboarding overlays, pause menu, terminal phase where eliminated teams watch their bar drain out. Getting all of this connected was the majority of this period.

The match wasn't actually starting after all that. Countdown never appeared, blood never emitted. Root cause was a spawn timing issue on the first frame — fixed by moving spawn dispatch to a later update pass. Floor boundary walls were added, and floors without players now fade out so the active floor is easier to read. Pillars between the camera and players turn semi-transparent.

Raw session notes

Mar 5–10 — Alpha systems complete & scene wiring

All 13 systems implemented and wired in Unity editor. First full match runnable.

Mar 11 — Floor visibility & boundary walls

Floor fades when no players present. Boundary walls added to arena edges.

Mar 14 — Match start fix & pillar occlusion

Fixed match not starting (spawn timing on first frame). Added pillar transparency when occluding players.

Players now have full control over their input setup — custom key bindings for keyboard and controller, with preset layouts (WASD, Arrows, IJKL, Numpad), conflict detection so two players can't claim the same keys, and swap confirmation when rebinding overlapping controls. Any combination of keyboards and controllers should just work.

The core arena mechanic was built: three floors stacked vertically, scrolling downward as altars are consumed. Blood emitters activate and deactivate by floor role. A vertical hazard descends from above. Visual proportions were overhauled after early testing showed everything looking wrong — platforms too thin, camera too far out, pillars piercing through multiple floors. All fixed.

The evolution bar was redesigned: instead of resetting when you evolve, the bar grows 30% wider and changes colour. Evolution feels like gaining something rather than starting over.

Raw session notes

Jan 22–26 — Input rebinding system

Full controller/keyboard rebind UI. Shared keyboard/controller support. Presets, conflict detection, swap confirmation, startup validation.

Feb 5 — Scrolling floor system

3-floor stacked arena. Chunk grid, gap generation, emitter/altar placement, BFS path validation. Scroll trigger on altar consumption. Vertical hazard.

Feb 14 — Pillar fix & visual overhaul

Fixed pillars piercing all floors. Floor spacing 10→5 units. Chunk thickness 0.2→1.0. Arena now looks proportional.

Feb 21 — Rename & alpha plan

Form-toggle action renamed across codebase. 13-system alpha implementation plan written with dependency tiers.

Feb 26 — Evolution bar redesign

Bar grows 30% wider on evolution instead of resetting. Tier accent colours added.

Why I automated my devlogs

The honest answer is that I kept not writing them. Not because nothing was happening — the game is being worked on almost every day — but because after a session I just want to stop. The last thing I want to do is write about what I just spent three hours doing.

So the devlogs never got written, and from the outside it probably looked like the game was dead. It wasn't. It just had no voice.

The automated log isn't a replacement for real devlogs. It's a proof-of-life signal. A way for anyone following the project to see that work is happening, even when I'm too tired to say so myself.