Development Log

Technical insights, development updates, and system architecture notes from building innovative AI frameworks and game systems.

Side Quests and Real Life

Sometimes you get sidetracked. Sometimes life happens. Updates will slow down, but the project continues.

Read Life & Reality
October 2025

Side Quests and Real Life

Got sidetracked with music composition and creative projects. Real life is happening, funds are tight, and VCR development is slowing down. Honest update about where things stand.

Read Life & Reality
September 2025

Layer 2 Blueprint: Documentation as Discovery

Why sometimes the most important work is the work that doesn't show up in the game. Seven diagrams, one breakthrough, and the psychology of invisible progress.

Read Architecture & Planning

The Phased Arbiter & Sound Engineering

Breaking the FSM Arbiter into digestible phases, discovering Suno's vocal magic, and building the infrastructure that turns chaos into gameplay

Read DevLog

Glitch Effects and Easter Eggs

Hero section polish, AI collaboration workflows, and subtle branding through interactive details

Read DevLog

Layer 1 Is the Beating Heart

Why fast phonetic analysis became the core of Voice Chain Reaction, and how architectural clarity leads to better game design decisions

Read Architecture & Planning

Layered Voice Detection Systems

Building a four-layer voice detection pipeline for Voice Chain Reaction, refactoring enemy systems, and celebrating personal milestones

Read Architecture & Planning

Project Pivots & Hardware Constraints

Navigating development challenges with aging hardware, strategic project pivots, and hard-learned lessons about documentation and version control

Read Project Management & Strategy
August 2025

Git LFS & Project Organization

The surprisingly complex process of setting up Git LFS for Unity projects, organizing vendor assets, and keeping repository history clean and meaningful

Read DevLog

Animation Integration & DOTS Experimentation

Integrating Mixamo animations with third-person controllers, building DOTS/ECS foundations, and reflecting on old hardware that's seen me through countries and careers

Read DevLog

Pivot to DOTS/ECS

Archiving the old behavior tree system and building fresh with DOTS/ECS. Sometimes the brave path is the right path.

Read Architecture & Planning

Refactoring Behavior Trees

Killing God Objects, cleaning up the Blackboard, and laying groundwork for DOTS/ECS migration

Read Architecture & Planning
July 2025

Securing the Brand

First devlog entry: Domain registration, project archival decisions, and strategic pivots

Read DevLog
this.happiness = this.loadStat('happiness', 80); this.lastUpdate = this.loadStat('lastUpdate', Date.now()); this.isShowingTemporaryMessage = false; this.idleMessageIndex = 0; this.idleMessages = [ 'Ready to code!', 'Scanning for bugs...', 'Thinking about algorithms...', 'Dreaming of clean code...', 'Optimizing thoughts...', 'Waiting for inspiration...', 'Contemplating refactoring...', 'Pondering edge cases...', 'Imagining perfect commits...', 'Debugging reality...' ]; this.initElements(); this.updateDisplay(); this.startDecay(); this.startIdleMessages(); this.bindEvents(); // Check if DevBot needs attention this.checkNeeds(); } initElements() { this.devbot = document.getElementById('devbot'); this.statusText = document.getElementById('status-text'); this.energyBar = document.getElementById('energy-bar'); this.happinessBar = document.getElementById('happiness-bar'); this.eyes = document.getElementById('eyes'); this.mouth = document.getElementById('mouth'); } bindEvents() { document.getElementById('feed-coffee').addEventListener('click', () => this.feedCoffee()); document.getElementById('give-bug').addEventListener('click', () => this.giveBug()); document.getElementById('pet-devbot').addEventListener('click', () => this.petDevBot()); } feedCoffee() { this.energy = Math.min(100, this.energy + 25); this.happiness = Math.min(100, this.happiness + 10); this.setMood('excited'); this.setStatus('*sip* Ahh, much better!', true); this.updateDisplay(); this.saveStat('energy', this.energy); this.saveStat('happiness', this.happiness); } giveBug() { if (this.energy < 20) { this.setStatus('Too tired to debug...', true); this.setMood('tired'); return; } this.energy = Math.max(0, this.energy - 15); this.happiness = Math.min(100, this.happiness + 20); this.setMood('happy'); this.setStatus('Found it! *squish*', true); this.updateDisplay(); this.saveStat('energy', this.energy); this.saveStat('happiness', this.happiness); } petDevBot() { this.happiness = Math.min(100, this.happiness + 15); this.setMood('happy'); this.setStatus('*purrs in binary*', true); this.updateDisplay(); this.saveStat('happiness', this.happiness); } setMood(mood) { this.devbot.className = 'tamagotchi-character'; if (mood) { this.devbot.classList.add(`character-${mood}`); } switch(mood) { case 'happy': this.mouth.textContent = '‿'; break; case 'tired': this.mouth.textContent = '﹏'; this.eyes.innerHTML = '--'; break; case 'excited': this.mouth.textContent = '◡'; break; default: this.mouth.textContent = '‿'; this.eyes.innerHTML = ''; } } setStatus(message, isTemporary = false) { this.statusText.textContent = message; if (isTemporary) { this.isShowingTemporaryMessage = true; setTimeout(() => { this.isShowingTemporaryMessage = false; this.updateStatusBasedOnStats(); }, 3000); } } updateStatusBasedOnStats() { if (this.isShowingTemporaryMessage) return; if (this.energy < 30 && this.happiness < 30) { this.setStatus('Need coffee and bugs...'); this.setMood('tired'); } else if (this.energy < 30) { this.setStatus('Could use some coffee...'); this.setMood('tired'); } else if (this.happiness < 30) { this.setStatus('Got any bugs to fix?'); this.setMood(null); } else if (this.energy > 80 && this.happiness > 80) { this.setStatus('Ready to refactor the world!'); this.setMood('happy'); } else { // Use idle messages for normal state this.showNextIdleMessage(); } } showNextIdleMessage() { if (this.isShowingTemporaryMessage) return; const message = this.idleMessages[this.idleMessageIndex]; this.setStatus(message); this.idleMessageIndex = (this.idleMessageIndex + 1) % this.idleMessages.length; } startIdleMessages() { // Rotate idle messages every 8 seconds setInterval(() => { this.updateStatusBasedOnStats(); }, 8000); } updateDisplay() { this.energyBar.style.width = `${this.energy}%`; this.happinessBar.style.width = `${this.happiness}%`; this.updateStatusBasedOnStats(); } startDecay() { setInterval(() => { // Slow decay over time this.energy = Math.max(0, this.energy - 1); this.happiness = Math.max(0, this.happiness - 0.5); this.updateDisplay(); this.saveStat('energy', this.energy); this.saveStat('happiness', this.happiness); }, 60000); // Every minute } checkNeeds() { const now = Date.now(); const hoursSinceLastUpdate = (now - this.lastUpdate) / (1000 * 60 * 60); // Apply decay based on time away const energyDecay = Math.min(this.energy, hoursSinceLastUpdate * 5); const happinessDecay = Math.min(this.happiness, hoursSinceLastUpdate * 3); this.energy = Math.max(0, this.energy - energyDecay); this.happiness = Math.max(0, this.happiness - happinessDecay); this.saveStat('lastUpdate', now); this.updateDisplay(); } loadStat(key, defaultValue) { const saved = localStorage.getItem(`devbot_${key}`); return saved ? parseFloat(saved) : defaultValue; } saveStat(key, value) { localStorage.setItem(`devbot_${key}`, value); } } // Initialize DevBot when page loads document.addEventListener('DOMContentLoaded', function() { new DevBotTamagotchi(); }); // Year Filter Functionality document.addEventListener('DOMContentLoaded', function() { const yearFiltersContainer = document.getElementById('year-filters'); const monthFilters = document.querySelectorAll('.month-filter'); const posts = document.querySelectorAll('[data-month]'); const pageTitle = document.querySelector('h1'); const pageDesc = document.querySelector('p[style*=\"color: var(--text-secondary)\"]'); const originalTitle = pageTitle.textContent; const originalDesc = pageDesc.textContent; // Dynamically generate year buttons based on existing posts function generateYearFilters() { const years = new Set(); posts.forEach(post => { if (post.dataset.month && post.dataset.month !== 'all') { const year = post.dataset.month.split('-')[0]; years.add(year); } }); // Clear existing buttons yearFiltersContainer.innerHTML = ''; // Add \"All Years\" button const allButton = document.createElement('button'); allButton.className = 'year-filter active'; allButton.dataset.year = 'all'; allButton.textContent = 'All'; yearFiltersContainer.appendChild(allButton); // Add year buttons in descending order Array.from(years).sort().reverse().forEach(year => { const button = document.createElement('button'); button.className = 'year-filter'; button.dataset.year = year; button.textContent = year; yearFiltersContainer.appendChild(button); }); // Bind events to new buttons bindYearFilterEvents(); } function bindYearFilterEvents() { const yearFilters = document.querySelectorAll('.year-filter'); yearFilters.forEach(filter => { filter.addEventListener('click', function() { const targetYear = this.dataset.year; // Update active year filter yearFilters.forEach(f => f.classList.remove('active')); this.classList.add('active'); // Update page title and description if (targetYear === 'all') { pageTitle.textContent = originalTitle; pageDesc.textContent = originalDesc; // Show all month filters monthFilters.forEach(mf => mf.style.display = 'block'); // Reset month filter to \"All Posts\" monthFilters.forEach(mf => mf.classList.remove('active')); document.querySelector('.month-filter[data-month=\"all\"]').classList.add('active'); // Show all posts posts.forEach(post => post.classList.remove('hidden')); // Show all month separators document.querySelectorAll('.month-separator').forEach(sep => sep.classList.remove('hidden')); } else { pageTitle.textContent = `Development Log - ${targetYear}`; pageDesc.textContent = `DevLog entries for ${targetYear}. Technical insights, development updates, and system architecture notes.`; // Filter month buttons to only show months from selected year monthFilters.forEach(mf => { const monthData = mf.dataset.month; if (monthData === 'all' || monthData.startsWith(targetYear)) { mf.style.display = 'block'; } else { mf.style.display = 'none'; } }); // Reset to show all posts for the year monthFilters.forEach(mf => mf.classList.remove('active')); document.querySelector('.month-filter[data-month=\"all\"]').classList.add('active'); // Show only posts from selected year posts.forEach(post => { if (post.dataset.month.startsWith(targetYear)) { post.classList.remove('hidden'); } else { post.classList.add('hidden'); } }); // Show only month separators for the selected year document.querySelectorAll('.month-separator').forEach(sep => { if (sep.dataset.month.startsWith(targetYear)) { const separatorMonth = sep.dataset.month; const visiblePostsInMonth = document.querySelectorAll(`.post-card[data-month=\"${separatorMonth}\"]:not(.hidden), .featured-post[data-month=\"${separatorMonth}\"]:not(.hidden)`); if (visiblePostsInMonth.length > 0) { sep.classList.remove('hidden'); } else { sep.classList.add('hidden'); } } else { sep.classList.add('hidden'); } }); } }); }); }