Files
novafarma/docs/V1_0_IMPLEMENTATION_PLAN.md
David Kotnik ce7d9e7d32 docs: Director Mode v1.0 - Master Directive & Implementation Plan
Added comprehensive directive and implementation plan for v1.0 release:
- Political systems (Lawyer, Mayor)
- Work automation (Zombie workers, 4-frame animations)
- Cinematic intro (Ken Burns, typewriter, blur-to-clear)
- Accessibility (ADHD mode, colorblind filters, asset gallery)

Current asset count: 1200+ images
Latest batch: 342 Style 32 assets
Status: Ready for autonomous execution
2026-01-04 18:56:17 +01:00

830 lines
22 KiB
Markdown

# 🎯 MRTVA DOLINA v1.0 - IMPLEMENTATION PLAN
**Generated**: 2026-01-04 18:52 CET
**Mode**: DIRECTOR MODE (Autonomous)
**Target**: v1.0 Release Build
**Status**: 🟢 READY TO EXECUTE
---
## 📅 TIMELINE
**Total Estimated Time**: 6-8 hours
**Phases**: 5
**Autonomous**: YES (// turbo-all)
---
## 🔢 PHASE 1: ASSET GENERATION (Style 32)
**Duration**: 2 hours
**Priority**: HIGH
### Tasks:
#### 1.1 Political NPCs
- [ ] `lawyer_wrinkled_suit_style32.png` - Odvetnik z aktovko in debelimi očali
- [ ] `mayor_post_apo_sash_style32.png` - Župan z lento, post-apo stil
#### 1.2 Animation Spritesheets (4 frames each)
- [ ] `milking_cow_4frame_style32.png` - Molža krave
- [ ] `blacksmithing_4frame_style32.png` - Kovaštvo z iskrami
- [ ] `chopping_wood_4frame_style32.png` - Sekanje drv
- [ ] `mining_4frame_style32.png` - Rudarjenje
#### 1.3 Zombie Workers
- [ ] `zombie_worker_purple_eyes_style32.png` - Zombie delavec z vijolično očmi
- [ ] `zombie_worker_carrying_style32.png` - Zombie nosi material
- [ ] `zombie_worker_mining_style32.png` - Zombie rudari
**Output Location**: `/assets/images/STYLE_32_SESSION_JAN_04/`
---
## 🎮 PHASE 2: GAME SYSTEMS IMPLEMENTATION
**Duration**: 2 hours
**Priority**: HIGH
### Tasks:
#### 2.1 Lawyer Service System
**File**: `/src/systems/LawyerService.js`
```javascript
export class LawyerService {
constructor(scene) {
this.scene = scene;
this.divorceFee = 50000;
this.lawyerCut = 0.25; // 25%
this.cooldownDays = 7;
this.lastDivorce = null;
}
processDivorce(player, partner) {
if (player.gold < this.divorceFee) {
return { success: false, reason: 'insufficient_funds' };
}
const lawyerCut = this.divorceFee * this.lawyerCut;
player.gold -= this.divorceFee;
this.scene.economy.lawyerGold += lawyerCut;
// Trigger town gossip
this.scene.gossipSystem.spread({
type: 'divorce',
source: player.name,
partner: partner.name,
severity: 'high'
});
this.lastDivorce = this.scene.time.getCurrentDay();
return { success: true, fee: this.divorceFee, lawyerCut };
}
canProcess() {
if (!this.lastDivorce) return true;
const daysSince = this.scene.time.getCurrentDay() - this.lastDivorce;
return daysSince >= this.cooldownDays;
}
}
```
#### 2.2 Mayor Service System
**File**: `/src/systems/MayorService.js`
```javascript
export class MayorService {
constructor(scene) {
this.scene = scene;
this.biomePermits = {
desert: 10000,
mountains: 15000,
jungle: 20000,
swamp: 12000,
arctic: 18000
};
this.taxRate = 0.10; // 10%
this.taxInterval = 30; // days
this.lastTaxCollection = 0;
}
issuePermit(player, biome) {
const cost = this.biomePermits[biome];
if (!cost) return { success: false, reason: 'invalid_biome' };
if (player.gold < cost) return { success: false, reason: 'insufficient_funds' };
player.gold -= cost;
player.biomeAccess[biome] = true;
this.scene.economy.mayorGold += cost;
return { success: true, biome, cost };
}
collectTax(player) {
const daysSince = this.scene.time.getCurrentDay() - this.lastTaxCollection;
if (daysSince < this.taxInterval) return { success: false, reason: 'too_soon' };
const taxAmount = Math.floor(player.gold * this.taxRate);
player.gold -= taxAmount;
this.scene.economy.mayorGold += taxAmount;
this.lastTaxCollection = this.scene.time.getCurrentDay();
return { success: true, amount: taxAmount };
}
}
```
#### 2.3 Zombie Worker Manager
**File**: `/src/systems/ZombieWorkerManager.js`
```javascript
export class ZombieWorkerManager {
constructor(scene) {
this.scene = scene;
this.workers = [];
this.efficiency = 0.75; // 75% of human speed
this.maintenanceCostPerDay = 50;
}
assignTask(zombie, task) {
zombie.currentTask = task;
zombie.workProgress = 0;
zombie.isWorking = true;
// Purple eye glow effect
zombie.eyes.setTint(0x9D4EDD); // violet
zombie.eyes.alpha = 0.8;
}
update(delta) {
this.workers.forEach(zombie => {
if (!zombie.isWorking) return;
const task = zombie.currentTask;
const progressRate = (this.efficiency * delta) / 1000;
zombie.workProgress += progressRate;
if (zombie.workProgress >= task.duration) {
this.completeTask(zombie, task);
}
});
}
completeTask(zombie, task) {
// Auto-store resources in base storage
this.scene.storage.add(task.resource, task.amount);
zombie.isWorking = false;
zombie.currentTask = null;
// Find next task
const nextTask = this.scene.taskQueue.getNext();
if (nextTask) {
this.assignTask(zombie, nextTask);
}
}
}
```
#### 2.4 Work Animation System
**File**: `/src/systems/WorkAnimationSystem.js`
```javascript
export class WorkAnimationSystem {
constructor(scene) {
this.scene = scene;
this.animations = {};
}
create() {
// Milking animation
this.scene.anims.create({
key: 'milking',
frames: this.scene.anims.generateFrameNumbers('milking_4frame', { start: 0, end: 3 }),
frameRate: 8,
repeat: -1
});
// Blacksmithing with sparks
this.scene.anims.create({
key: 'blacksmithing',
frames: this.scene.anims.generateFrameNumbers('blacksmith_4frame', { start: 0, end: 3 }),
frameRate: 6,
repeat: -1
});
// Chopping wood
this.scene.anims.create({
key: 'chopping',
frames: this.scene.anims.generateFrameNumbers('chop_wood_4frame', { start: 0, end: 3 }),
frameRate: 7,
repeat: -1
});
// Mining
this.scene.anims.create({
key: 'mining',
frames: this.scene.anims.generateFrameNumbers('mining_4frame', { start: 0, end: 3 }),
frameRate: 8,
repeat: -1
});
}
playAnimation(sprite, animKey) {
sprite.play(animKey);
// Add particle effects for specific frames
if (animKey === 'blacksmithing') {
sprite.on('animationupdate', (anim, frame) => {
if (frame.index === 2) { // Impact frame
this.scene.particles.emitSparks(sprite.x, sprite.y);
}
});
}
}
}
```
---
## 🎬 PHASE 3: CINEMATIC INTRO SYSTEM
**Duration**: 2 hours
**Priority**: MEDIUM
### Tasks:
#### 3.1 Intro Scene with Ken Burns Effect
**File**: `/src/scenes/IntroScene.js`
```javascript
export class IntroScene extends Phaser.Scene {
constructor() {
super({ key: 'IntroScene' });
}
create() {
this.cameras.main.setBackgroundColor('#000000');
// Scene 1: Abandoned farm with Ken Burns zoom
this.showScene1();
}
showScene1() {
const bg = this.add.image(0, 0, 'intro_farm_abandoned').setOrigin(0);
bg.setScale(1.2); // Start zoomed in
// Ken Burns: slow zoom out + pan
this.tweens.add({
targets: bg,
scale: 1.0,
x: -50,
y: -30,
duration: 5000,
ease: 'Sine.easeInOut'
});
// Typewriter text
this.showTypewriter(
"Leta 2084, Dolina se ni več obranila...",
100, 500,
() => this.crossFadeToScene2()
);
}
showTypewriter(text, x, y, onComplete) {
const textObj = this.add.text(x, y, '', {
fontSize: '24px',
fontFamily: 'Georgia',
color: '#FFFFFF',
stroke: '#000000',
strokeThickness: 4
});
let charIndex = 0;
const typeSpeed = 60; // ms per character
const timer = this.time.addEvent({
delay: typeSpeed,
repeat: text.length - 1,
callback: () => {
textObj.text += text[charIndex];
charIndex++;
if (charIndex === text.length && onComplete) {
this.time.delayedCall(2000, onComplete);
}
}
});
}
crossFadeToScene2() {
this.cameras.main.fadeOut(1500, 0, 0, 0);
this.cameras.main.once('camerafadeoutcomplete', () => {
this.showScene2();
this.cameras.main.fadeIn(1500, 0, 0, 0);
});
}
showScene2() {
this.children.removeAll();
const bg = this.add.image(400, 300, 'intro_lab_flash').setScale(1.0);
this.showTypewriter(
"Incident je pustil posledice, ki jih nihče ni pričakoval...",
100, 500,
() => this.blurToGameplay()
);
}
blurToGameplay() {
// Blur-to-clear transition
const blur = this.cameras.main.postFX.addBlur(0, 2, 2, 10);
this.cameras.main.fadeOut(2000, 0, 0, 0);
this.tweens.add({
targets: blur,
strength: 0,
duration: 2000,
onComplete: () => {
this.scene.start('GameScene'); // Start gameplay
}
});
}
}
```
#### 3.2 Voice Cloning Pipeline
**File**: `/tools/voice_clone.py`
```python
import edge_tts
import asyncio
from pydub import AudioSegment
from pydub.effects import low_pass_filter, high_pass_filter
async def clone_voice_with_radio_filter(text, lang='sl', output_path='narration.mp3'):
"""
Clone user's voice and apply radio filter
"""
# Use Edge TTS for voice generation
communicate = edge_tts.Communicate(text, voice=f"{lang}-SL-PetraNeural")
await communicate.save(output_path)
# Apply radio filter (post-apocalyptic aesthetic)
audio = AudioSegment.from_file(output_path)
# Radio effect: bandpass filter + compression
radio_audio = high_pass_filter(audio, 300) # Cut below 300Hz
radio_audio = low_pass_filter(radio_audio, 3000) # Cut above 3kHz
radio_audio = radio_audio + 3 # Increase volume slightly
radio_audio.export(output_path, format="mp3")
print(f"✅ Voice generated with radio filter: {output_path}")
# Generate all languages
languages = {
'sl': 'sl-SL-PetraNeural', # Slovenian (use as base)
'en': 'en-US-GuyNeural', # English
'de': 'de-DE-ConradNeural', # German
'it': 'it-IT-DiegoNeural', # Italian
'zh': 'zh-CN-YunxiNeural' # Chinese
}
async def generate_all_narrations():
script = "Leta 2084, Dolina se ni več obranila..."
for lang_code, voice in languages.items():
await clone_voice_with_radio_filter(
script,
lang_code,
f"assets/audio/intro_{lang_code}.mp3"
)
asyncio.run(generate_all_narrations())
```
---
## 🖼️ PHASE 4: ACCESSIBILITY & ASSET GALLERY
**Duration**: 1.5 hours
**Priority**: MEDIUM
### Tasks:
#### 4.1 Asset Gallery (Thumbnail Grid)
**File**: `/tools/asset_gallery.html`
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DolinaSmrti Asset Gallery</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #1a1a1a;
color: #fff;
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
color: #9D4EDD;
}
.search-bar {
width: 100%;
max-width: 600px;
margin: 0 auto 30px;
display: block;
padding: 12px;
font-size: 16px;
border: 2px solid #9D4EDD;
border-radius: 8px;
background: #2a2a2a;
color: #fff;
}
.filter-buttons {
text-align: center;
margin-bottom: 30px;
}
.filter-btn {
padding: 8px 16px;
margin: 5px;
border: 2px solid #9D4EDD;
background: #2a2a2a;
color: #fff;
cursor: pointer;
border-radius: 6px;
transition: all 0.3s;
}
.filter-btn:hover, .filter-btn.active {
background: #9D4EDD;
color: #000;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 20px;
padding: 20px;
}
.asset-card {
background: #2a2a2a;
border-radius: 12px;
padding: 10px;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
cursor: pointer;
}
.asset-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(157, 78, 221, 0.4);
}
.asset-card img {
width: 128px;
height: 128px;
object-fit: contain;
border-radius: 8px;
background: #1a1a1a;
}
.asset-card .filename {
margin-top: 8px;
font-size: 12px;
color: #aaa;
word-break: break-all;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal.active { display: flex; }
.modal img {
max-width: 90%;
max-height: 90%;
border-radius: 12px;
}
</style>
</head>
<body>
<h1>🎨 DolinaSmrti Asset Gallery</h1>
<input type="text" class="search-bar" id="search" placeholder="Search assets...">
<div class="filter-buttons">
<button class="filter-btn active" data-filter="all">All (342)</button>
<button class="filter-btn" data-filter="buildings">Buildings</button>
<button class="filter-btn" data-filter="npcs">NPCs</button>
<button class="filter-btn" data-filter="terrain">Terrain</button>
<button class="filter-btn" data-filter="interior">Interior</button>
<button class="filter-btn" data-filter="weapons">Weapons</button>
<button class="filter-btn" data-filter="tools">Tools</button>
<button class="filter-btn" data-filter="ui">UI</button>
</div>
<div class="gallery-grid" id="gallery"></div>
<div class="modal" id="modal">
<img id="modal-img" src="" alt="">
</div>
<script>
const assetPath = '../assets/images/STYLE_32_SESSION_JAN_04/';
const gallery = document.getElementById('gallery');
const modal = document.getElementById('modal');
const modalImg = document.getElementById('modal-img');
const search = document.getElementById('search');
let assets = [];
// Load assets from directory
fetch(assetPath)
.then(r => r.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const links = [...doc.querySelectorAll('a')];
assets = links
.filter(a => a.href.endsWith('.png'))
.map(a => ({
name: a.textContent,
path: assetPath + a.textContent,
category: detectCategory(a.textContent)
}));
renderGallery(assets);
});
function detectCategory(filename) {
if (filename.match(/^(barn|bakery|church|house|farmhouse|tavern|windmill)/i)) return 'buildings';
if (filename.match(/^npc/i)) return 'npcs';
if (filename.match(/^terrain/i)) return 'terrain';
if (filename.match(/^interior/i)) return 'interior';
if (filename.match(/weapon/i)) return 'weapons';
if (filename.match(/tool/i)) return 'tools';
if (filename.match(/(button|icon|bar|panel)/i)) return 'ui';
return 'misc';
}
function renderGallery(items) {
gallery.innerHTML = items.map(asset => `
<div class="asset-card" data-category="${asset.category}" onclick="openModal('${asset.path}')">
<img src="${asset.path}" alt="${asset.name}" loading="lazy">
<div class="filename">${asset.name}</div>
</div>
`).join('');
}
function openModal(src) {
modalImg.src = src;
modal.classList.add('active');
}
modal.addEventListener('click', () => modal.classList.remove('active'));
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const filter = btn.dataset.filter;
const cards = document.querySelectorAll('.asset-card');
cards.forEach(card => {
if (filter === 'all' || card.dataset.category === filter) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
});
});
// Search
search.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
const filtered = assets.filter(a => a.name.toLowerCase().includes(query));
renderGallery(filtered);
});
</script>
</body>
</html>
```
#### 4.2 ADHD Focus Mode
**File**: `/src/systems/AccessibilityManager.js`
```javascript
export class AccessibilityManager {
constructor(scene) {
this.scene = scene;
this.adhdMode = false;
this.colorblindMode = null;
}
enableADHDMode() {
this.adhdMode = true;
// Reduce animations
this.scene.tweens.timeScale = 0.7; // Slower, more predictable
// Highlight interactables
this.scene.interactables.forEach(obj => {
obj.setStrokeStyle(4, 0xFFFF00, 1); // Yellow outline
});
// Simplify UI
this.scene.ui.setAlpha(0.9);
this.scene.ui.setScale(1.1); // Slightly larger
// Increase contrast
this.scene.cameras.main.setAlpha(1.5);
console.log('✅ ADHD Focus Mode enabled');
}
setColorblindFilter(type) {
this.colorblindMode = type;
const filters = {
protanopia: { r: 0.56667, g: 0.43333, b: 0 },
deuteranopia: { r: 0.625, g: 0.375, b: 0 },
tritanopia: { r: 0.95, g: 0.05, b: 0 },
monochrome: { r: 0.299, g: 0.587, b: 0.114 }
};
if (filters[type]) {
this.scene.cameras.main.setTint(Phaser.Display.Color.GetColor(
filters[type].r * 255,
filters[type].g * 255,
filters[type].b * 255
));
}
}
}
```
#### 4.3 Smart Asset Organization Script
**File**: `/scripts/organize_assets.py`
```python
import os
import shutil
from pathlib import Path
SOURCE_DIR = "assets/images/STYLE_32_SESSION_JAN_04"
TARGET_STRUCTURE = {
"characters": {
"gronk": ["gronk"],
"kai": ["kai"],
"ana": ["ana"],
"zombies": ["zombie"]
},
"buildings": ["barn", "bakery", "church", "farmhouse", "house", "tavern", "windmill", "workshop"],
"biomes": {
"desert": ["desert"],
"mountains": ["mountain"],
"jungle": ["jungle"],
"swamp": ["swamp"]
},
"props": ["barrel", "chest", "campfire", "crate", "grave"],
"ui": ["button", "icon", "bar", "panel", "slot"],
"interior": ["interior"],
"weapons": ["weapon"],
"tools": ["tool"],
"terrain": ["terrain"]
}
def organize_assets():
"""Reorganize assets into smart folder structure"""
for category, patterns in TARGET_STRUCTURE.items():
if isinstance(patterns, dict):
# Nested structure
for subfolder, subpatterns in patterns.items():
organize_category(category, subfolder, subpatterns)
else:
organize_category(category, None, patterns)
def organize_category(category, subfolder, patterns):
target_path = Path(SOURCE_DIR) / category
if subfolder:
target_path = target_path / subfolder
target_path.mkdir(parents=True, exist_ok=True)
for file in Path(SOURCE_DIR).glob("*.png"):
filename = file.name.lower()
if any(pattern in filename for pattern in patterns):
new_path = target_path / file.name
shutil.copy(file, new_path)
print(f"{file.name}{category}/{subfolder or ''}")
if __name__ == "__main__":
organize_assets()
print("🎉 Asset organization complete!")
```
---
## ✅ PHASE 5: TESTING & FINAL COMMIT
**Duration**: 0.5 hours
**Priority**: CRITICAL
### Tasks:
- [ ] Test lawyer service (divorce flow, gossip trigger, gold calculation)
- [ ] Test mayor service (permit purchase, tax collection)
- [ ] Test zombie workers (assignment, purple eyes, auto-storage)
- [ ] Test work animations (all 4 cycles, particle effects)
- [ ] Test intro sequence (Ken Burns, typewriter, blur-to-clear)
- [ ] Test asset gallery (thumbnail grid, search, filters, modal)
- [ ] Test ADHD mode (reduced animations, highlighted interactables)
- [ ] Test colorblind filters (all 4 modes)
- [ ] Verify asset organization (smart folders created correctly)
### Final Commit:
```bash
git add .
git commit -m "feat(v1.0): Director Mode - Complete Political, Automation, Cinematic & Accessibility Systems
SYSTEMS IMPLEMENTED:
✅ Lawyer NPC (divorce 50k, 25% cut, gossip)
✅ Mayor NPC (biome permits, tax collection)
✅ Zombie Workers (24/7, purple eyes, auto-storage)
✅ Work Animations (milking, blacksmithing, chopping, mining)
✅ Cinematic Intro (Ken Burns, typewriter, blur-to-clear)
✅ Voice Cloning (multi-language, radio filter)
✅ Asset Gallery (342+ thumbnails, search, filters)
✅ ADHD Focus Mode (reduced animations, highlighted UI)
✅ Colorblind Filters (protanopia, deuteranopia, tritanopia, monochrome)
✅ Smart Asset Organization (characters, biomes, buildings, props)
STATUS: Mrtva Dolina v1.0 COMPLETE 🎬
Assets: 342 images, 170MB
Code: ~2000 new lines
Systems: 9 new + 4 enhanced"
```
---
## 📊 DELIVERABLES SUMMARY
### Assets Generated: ~15
- 2 political NPCs (lawyer, mayor)
- 4 animation spritesheets (4 frames each = 16 frames)
- 3 zombie worker variants
### Code Written: ~2000 lines
- LawyerService.js (150 lines)
- MayorService.js (120 lines)
- ZombieWorkerManager.js (180 lines)
- WorkAnimationSystem.js (140 lines)
- IntroScene.js (200 lines)
- AccessibilityManager.js (100 lines)
- asset_gallery.html (250 lines)
- voice_clone.py (80 lines)
- organize_assets.py (60 lines)
### Systems Implemented: 9
1. Lawyer Service
2. Mayor Service
3. Zombie Worker Manager
4. Work Animation System
5. Cinematic Intro Scene
6. Voice Cloning Pipeline
7. Asset Gallery
8. ADHD Focus Mode
9. Colorblind Filters
---
## 🎯 SUCCESS CRITERIA
✅ All political NPCs rendered and functional
✅ Work animations smooth and particle effects working
✅ Zombie workers operate 24/7 with purple eyes
✅ Intro plays with Ken Burns, typewriter, and blur
✅ Asset gallery shows all 342+ images with search
✅ ADHD mode reduces cognitive load effectively
✅ Colorblind filters functional for all 4 types
✅ Assets organized in smart folder structure
✅ All systems tested and bug-free
✅ Commit message comprehensive and accurate
---
**EXECUTION MODE**: AUTONOMOUS (// turbo-all)
**APPROVAL REQUIRED**: NO
**PROCEED**: YES
---
**END OF IMPLEMENTATION PLAN**
**Ready to execute on your command.**