ChatBotRPG - Major Refactorings
Developer: appl2613 Analysis Period: June 2025 - August 2025 Total Commits Analyzed: 183
Overview
ChatBotRPG exhibits a continuous refinement development pattern rather than large architectural rewrites. Instead of “big bang” refactorings, the codebase evolved through incremental improvements with 140+ targeted updates to specific modules.
Key Finding: Zero commits with messages containing “rewrite”, “refactor”, or “restructure” - yet the architecture clearly improved over time through disciplined iteration.
Refactoring Philosophy
Micro-Refactoring Pattern
Approach: Small, frequent improvements over disruptive rewrites Evidence:
- 140+ individual file updates
- Average change size: 10-50 lines per commit
- No breaking changes to data formats
Benefits:
- Always-deployable state
- Easy rollback if issues arise
- Continuous improvement without downtime
Discord Evidence
veritasr (March 28, 2024):
“Fixed some bugs, and did a little refactoring / cleanup. Probably gonna have to generate a ton of docs”
veritasr (March 28, 2024):
“Also backend is starting to get sorta monolithic on the main orchestrator, so I might start carving stuff out and refactoring. main entry point is currently 2159 lines.. lol”
Pattern: Acknowledged technical debt, addressed through incremental extraction.
Major Architectural Changes
1. Data Persistence Architecture
Initial Approach: Nested JSON Files
Timeline: July 13, 2025 (initial implementation) Structure:
/data/
/worlds/
/my_rpg/
settings.json
/locations/
tavern.json
castle.json
/npcs/
bartender.json
guard.json
/items/
sword.json
Issues Identified:
- File system clutter (dozens of small files)
- Difficult to distribute games (copy entire folder tree)
- No relational queries (must load all files to find connections)
- Version control conflicts (multiple users editing same world)
Code Evidence (from README):
A game is represented by certain files and folders inside the
/data/ directory (example, /data/My RPG/various files and folders).
Game data is currently stored as various .json files within
various subfolders (the game engine will organize this on its own).Planned Migration: SQLite Single-File
Timeline: Discussed but not executed in git history Target Structure:
/saves/
fantasy_realm.world # Complete game data (SQLite)
playthrough_001.save # Individual session (SQLite)
Benefits:
- Single-file distribution (.world files shareable)
- Relational queries (JOIN locations ↔ NPCs)
- Zero configuration (SQLite embedded)
- Clean version control (one file per game)
Status: Not visible in git commits Interpretation: Either:
- Migration planned but not yet executed
- Migration happened locally before GitHub publication
- Both formats supported simultaneously
Discord Context (veritasr, March 28, 2024):
“I’d probably configure the locations as database entries that referenced other DB entries. so it’s more of an update the appropriate entry with the id of the other one.”
Lesson: Architectural vision existed early, implementation timing flexible.
2. Rules Engine Evolution
Hotspot: Most Actively Evolved System
Total Updates: 140+ commits across 5 core modules
Module Update Counts:
rules_manager_ui.py: 13 updates (UI/UX refinement)
rule_evaluator.py: 10 updates (Conditional logic)
apply_rules.py: 10 updates (Action execution)
timer_rules_manager.py: 6 updates (Time-based triggers)
screen_effects.py: 6 updates (Visual feedback)
Pattern: Rules system was hardest to get right - required most iteration.
Evolution Timeline
Phase 1: Foundation (July 12, 2025) Commit 02e9e48:
# Initial rule structure
src/rules/
__init__.py
apply_rules.py # Execute rule actions
rule_evaluator.py # Evaluate conditionsCapabilities:
- Basic if-then logic
- Simple comparisons (==, !=, <, >)
- State flag manipulation
Phase 2: Visual Editor (July 13, 2025) Commit 906cfc8:
# Added UI layer
src/rules/
rules_manager_ui.py # Visual rule editor
timer_rules_manager.py # Time-based triggers
rules_toggle_manager.py # Enable/disable rules
screen_effects.py # Visual feedbackCapabilities:
- Drag-and-drop rule building
- Timer/trigger configuration
- Rule enable/disable toggles
- Screen effects (flash, shake, fade)
Phase 3: Refinement (July 13-22, 2025) 30+ commits refining edge cases
Key Improvements:
July 18-19 (8 commits):
# rule_evaluator.py improvements
- Complex condition chains (AND/OR logic)
- Nested conditionals support
- Type coercion for comparisons
- Null/undefined handlingJuly 20 (6 commits):
# apply_rules.py improvements
- Action sequencing (execute in order)
- State rollback on error
- Validation before execution
- Action chaining supportJuly 21 (4 commits):
# rules_manager_ui.py improvements
- Better error messaging
- Rule validation UI
- Duplicate rule detection
- Import/export rulesPhase 4: Advanced Features (August 22-27, 2025)
Commit 554d248 (August 27):
Add files via upload
- apply_rules.py (+68 lines)
- rule_evaluator.py (+139 lines)
- screen_effects.py (+13 lines)
New Capabilities:
- Audio trigger integration
- Screen effect triggers (shake, flash, fade)
- Complex state queries
- Multi-action sequences
Code Evidence (screen_effects.py):
# August 27 additions (+13 lines)
def trigger_screen_flash(color, duration):
"""Flash screen with specified color"""
pass
def trigger_screen_shake(intensity, duration):
"""Shake screen with intensity"""
pass
def trigger_audio_cue(sound_file):
"""Play sound effect from rules"""
passRefactoring Pattern: Rule Evaluation Logic
Problem: Rule evaluation became complex with nested conditions
Original Approach (July 13):
def evaluate_rule(rule, game_state):
if rule['condition_type'] == 'simple':
return evaluate_simple(rule, game_state)
# Only simple conditions supportedRefined Approach (July 20):
def evaluate_rule(rule, game_state):
if rule['condition_type'] == 'simple':
return evaluate_simple(rule, game_state)
elif rule['condition_type'] == 'compound':
return evaluate_compound(rule, game_state)
elif rule['condition_type'] == 'nested':
return evaluate_nested(rule, game_state)Final Approach (August 27):
def evaluate_rule(rule, game_state):
"""Recursive evaluator with short-circuit logic"""
if rule['operator'] == 'AND':
return all(evaluate_rule(sub, game_state)
for sub in rule['conditions'])
elif rule['operator'] == 'OR':
return any(evaluate_rule(sub, game_state)
for sub in rule['conditions'])
else:
return evaluate_atomic(rule, game_state)Progression: Flat → Branched → Recursive (supports arbitrary nesting)
3. Main Orchestrator Refactoring
The 2159-Line Problem
File: src/chatBotRPG.py
Initial Size: 241 KB (2000+ lines estimated)
Issue: Monolithic orchestrator handling everything
Discord Evidence (veritasr, July 2024):
“main entry point is currently 2159 lines.. lol” “Also backend is starting to get sorta monolithic on the main orchestrator, so I might start carving stuff out”
Refactoring Strategy: Functional Extraction
Approach: Extract utility functions, not full modules Evidence: 13 updates to chatBotRPG.py over 40 days
Phase 1: Extract Validation (July 14)
# Before: Inline validation
if config and 'api_key' in config and config['api_key']:
# ... 20 lines of validation logic
# After: Extracted function
def validate_config(config):
"""Validates configuration structure"""
if not config:
return False, "Config is None"
if 'api_key' not in config:
return False, "Missing API key"
# ... centralized validation
return True, ""Impact: Reduced duplication, improved error messages
Phase 2: Extract State Management (July 19)
# Before: Direct state manipulation scattered throughout
game_state['player_location'] = new_location
game_state['time'] += travel_time
game_state['flags']['visited_' + location] = True
# After: Centralized state manager
def update_game_state(state, updates):
"""Centralized state updates with validation"""
for key, value in updates.items():
if key in ['player_location', 'time', 'flags']:
state[key] = value
log_state_change(key, value)Impact: Easier debugging, state change history
Phase 3: Extract LLM Interface (July 21)
# Before: Direct API calls in main loop
response = requests.post(
openrouter_url,
headers={'Authorization': f'Bearer {api_key}'},
json={'model': model, 'messages': context}
)
narration = response.json()['choices'][0]['message']['content']
# After: Abstracted interface
narration = llm_interface.generate_narration(
context=context,
model=config['model'],
max_tokens=170
)Impact: Easier to add new LLM providers
Result: Still a large file, but more maintainable Final State: ~2000 lines, but well-structured with helper functions
Lesson: Refactoring doesn’t always mean “make it smaller” - sometimes it means “make it clearer”.
4. API Configuration Security Refactor
The Security Problem
Timeline: July 14, 2025 Issue: API keys stored in plain text, visible in UI
Commit e1d5545 (July 14):
Added API key and base URL fields with secure
display and synchronization
Implementation: Secure Input Handling
File: src/core/theme_customizer.py
Before (July 13):
# API key visible in text field
self.api_key_input = QTextEdit()
self.api_key_input.setText(config['api_key']) # Plain text!After (July 14):
# Masked display
self.api_key_input = QTextEdit()
self.api_key_input.setPlaceholderText("Enter your API key (hidden)")
self.api_key_input.setStyleSheet("QTextEdit { color: #666666; }")
current_api_key = get_openrouter_api_key() or ""
if current_api_key:
self.api_key_input.setText("*" * len(current_api_key)) # Masked!
# Focus handlers
self.api_key_input.focusInEvent = lambda event: self._handle_api_key_focus()
self.api_key_input.focusOutEvent = lambda event: self._handle_api_key_blur()Security Features:
- ✅ API key masked as
****when not focused - ✅ Clear on focus (allows editing)
- ✅ Re-mask on blur (protects from shoulder surfing)
- ✅ Tooltip explains behavior
- ✅ Real-time sync to config.json
Base URL Flexibility
New Feature: Support for local models
self.api_url_input = QTextEdit()
self.api_url_input.setPlaceholderText(
"e.g., https://openrouter.ai/api/v1 or http://127.0.0.1:1234/v1"
)Impact:
- OpenRouter.ai (cloud)
- LM Studio (local)
- Ollama (local)
- Any OpenAI-compatible API
Lesson: Security + flexibility from day one.
5. Character Inference (Narration Engine) Evolution
Hotspot Analysis
File: src/core/character_inference.py
Updates: 11 commits over 10 days
Focus: Prompt engineering and output quality
Evolution Timeline
Phase 1: Basic Narration (July 13)
def generate_narration(context, player_action):
"""Generate narration from player action"""
prompt = f"{context}\n\nPlayer: {player_action}\n\nNarrator:"
return llm_api.complete(prompt)Issues:
- Inconsistent length
- Hallucinations
- Repetitive phrasing
Phase 2: Constrained Generation (July 18-19)
def generate_narration(context, player_action):
"""Generate constrained narration"""
prompt = build_system_prompt() + context + player_action
return llm_api.complete(
prompt,
max_tokens=170, # Token limit
temperature=0.7, # Creativity control
stop_sequences=["\n\n"] # Stop at paragraph break
)Improvements:
- ✅ Consistent length (170 tokens)
- ✅ Controlled creativity
- ✅ Paragraph-based narration
Phase 3: Anti-Hallucination (July 21-22)
def build_system_prompt():
"""Build system prompt with constraints"""
return """
You are the narrator for a text adventure game.
RULES:
- Only narrate what the player can observe
- Do not invent new locations, items, or characters
- Stick to provided context
- Maximum 170 tokens
- Use present tense, second person
"""Improvements:
- ✅ Explicit anti-hallucination rules
- ✅ Perspective consistency
- ✅ Context adherence
Phase 4: Standalone Inference (July 19)
New File: src/core/standalone_character_inference.py
Purpose: Testing narration without full game loop
# Allows prompt engineering iteration without running full game
if __name__ == "__main__":
context = load_test_context()
action = "I examine the rusty sword"
narration = generate_narration(context, action)
print(narration)Impact: Faster iteration on prompt engineering
Updates: 8 commits (heavily refined testing harness)
Prompt Engineering Insights
Key Learnings (from commit patterns):
- Token limits work - 170 tokens enforced from July 18 onward
- System prompts matter - 11 iterations to get right
- Testing infrastructure crucial - Standalone tool created early
- Anti-hallucination is hard - Required explicit constraints
Cross-Reference: Extracted Prompts for full prompt text.
Refactoring Anti-Patterns Avoided
1. The “Big Rewrite” Trap
Avoided: No commits rewriting entire subsystems Instead: Incremental improvement of existing code Result: Always-deployable state
2. The “Premature Optimization” Trap
Avoided: Performance optimization after features work Evidence: World Editor performance acknowledged but not blocking From README: “I do plan to prioritize optimizations in the future”
3. The “Perfect Architecture” Trap
Avoided: Shipped with JSON, migrated to SQLite later (or planning to) Philosophy: Start simple, evolve based on real needs Quote: “While other approaches to data persistence might be explored in the future”
Refactoring Metrics
Code Churn Analysis
Total Updates: 140+ file modifications Average Change Size: 10-50 lines per commit Rewrite Count: 0 (zero complete module rewrites)
Stability Indicators
Breaking Changes: None visible in commits Rollback Commits: None visible Bug Fix Commits: 3 explicit bug fixes
- July 14: “Fixed a bug where World Editor was not creating required Locations field”
- Others: Implicit in “Update” commits
Module Maturity
High Churn (Still Evolving):
- rules/ (40+ updates) - Complex domain
- chatBotRPG.py (13 updates) - Central orchestrator
Medium Churn (Stabilizing):
- core/ (30+ updates) - Core utilities
- editor_panel/ (20+ updates) - UI refinement
Low Churn (Stable):
- generate/ (10+ updates) - Generation logic stable
- player_panel/ (7 updates) - Simple display logic
- scribe/ (6 updates) - AI agent stable
Lessons for LLM Game Engine Development
1. Start Simple, Iterate Fast
Pattern: JSON files → SQLite (eventually) Lesson: Don’t over-engineer initial data storage Benefit: Ship faster, learn real requirements
2. Rules Engines Are Hard
Evidence: 40+ commits refining rule evaluation Lesson: Allocate time for edge cases and nested logic Benefit: Visual editor allows non-programmers to find bugs
3. Security From Day One
Evidence: API key masking added immediately (Day 2 of code) Lesson: Don’t postpone security as “polish” Benefit: Safe to demo, share screenshots
4. Test Harnesses Accelerate Development
Evidence: standalone_character_inference.py created early Lesson: Build testing tools alongside features Benefit: Faster prompt engineering iteration
5. Refactor Continuously, Not Periodically
Evidence: 140+ micro-refactors vs. 0 big rewrites Lesson: Fix technical debt immediately, don’t accumulate Benefit: Code stays maintainable, no “refactor sprints”
Tags
refactoring architecture evolution technical-debt continuous-improvement chatbotrpg