| name | optimization-as-play |
| description | Make optimization itself fun - efficiency puzzles and throughput as gameplay |
Optimization as Play: Making Efficiency The Core Gameplay Loop
Purpose
This skill teaches how to design games where optimization itself is the fun, not a chore you do to progress. Transform production systems, efficiency puzzles, and throughput challenges into engaging player-driven gameplay loops.
Factory games (Factorio, Satisfactory), optimization puzzles (Opus Magnum, SpaceChem), and logistics simulations succeed when players spend hours perfecting their systems for the intrinsic satisfaction of watching efficient production.
This skill is for Wave 2 (Specific Applications) of the systems-as-experience skillpack, building on emergent gameplay foundations.
When to Use This Skill
Use this skill when:
- Core gameplay loop is building/managing production systems
- Measurable performance metrics make sense in-world (throughput, efficiency, cost)
- Multiple valid solutions exist (not one optimal path)
- Players enjoy efficiency for its own sake (intrinsic motivation)
- System complexity grows through player mastery (simple → complex)
- Bottleneck identification is core to gameplay
- Community can share and compare solutions
- Visual/spatial problem-solving appeals to target audience
Do NOT use this skill when:
- Action/reaction gameplay is primary (use real-time challenge design instead)
- Narrative-driven experience (optimization competes with story pacing)
- Players must optimize to progress (makes it feel like homework)
- Only one correct solution exists (reduces to following instructions)
- Metrics are hidden or meaningless in-world
- Instant gratification is required (optimization rewards delayed satisfaction)
Key Insight: Optimization-as-play works when it's optional but irresistible. Players choose to optimize because it feels good, not because they're forced to.
Core Philosophy: The Optimization Gameplay Loop
The Fundamental Loop
IDENTIFY BOTTLENECK
↓
HYPOTHESIZE SOLUTION
↓
IMPLEMENT
↓
MEASURE
↓
ITERATE
↓
(repeat with new bottleneck)
This loop must be:
- Visible: Players clearly see what's slow
- Fast: Seconds to iterate, not minutes
- Satisfying: Improvements feel rewarding
- Progressive: Each optimization reveals new bottlenecks
- Deep: Mastery unlocks breakthrough solutions
Why Optimization Can Be Fun
Bad optimization (homework):
- Required to progress
- One correct answer
- Math-heavy calculation
- No visual feedback
- Linear improvements
Good optimization (play):
- Optional but compelling
- Multiple valid approaches
- Experimental discovery
- Satisfying visuals/audio
- Breakthrough moments
The Three Pillars of Optimization-as-Play
- Measurement: Players can SEE performance (throughput, efficiency, bottlenecks)
- Tradeoffs: Multiple dimensions to optimize (speed vs cost vs space vs power)
- Satisfaction: Improvements FEEL good (juice, celebration, visible progress)
SECTION 1: Visible Performance Metrics
The Visibility Principle
"You can't optimize what you can't measure, and players won't measure what you don't show them."
Core Metrics to Visualize
class PerformanceMetrics:
"""
Essential metrics for optimization gameplay.
All displayed in real-time, not hidden in menus.
"""
def __init__(self):
# Throughput: Items per unit time
self.items_per_minute = 0.0
self.target_throughput = 60.0 # Goal to hit
# Efficiency: % of theoretical maximum
self.efficiency_percent = 0.0 # 0-100%
# Bottleneck Status: Where is the slowdown?
self.bottleneck_type = None # INPUT_STARVED, OUTPUT_BLOCKED, etc.
self.bottleneck_location = None # Which building/node
# Resource Usage: Cost to run
self.power_consumption = 0.0
self.power_budget = 100.0
# Spatial Efficiency: How compact is layout
self.footprint_size = 0 # Grid squares used
self.building_count = 0
# Utilization: Are buildings idle or running?
self.avg_utilization = 0.0 # 0-100% average across all buildings
class MetricsDisplay:
"""
Real-time HUD showing performance metrics.
Updated every frame for immediate feedback.
"""
def render(self, metrics):
# Throughput with color coding
throughput_color = self.get_color_for_value(
metrics.items_per_minute,
metrics.target_throughput
)
draw_text(f"{metrics.items_per_minute:.1f}/min",
color=throughput_color,
size="LARGE")
# Efficiency percentage
efficiency_bar(metrics.efficiency_percent)
# Bottleneck alert (if exists)
if metrics.bottleneck_type:
draw_alert(f"BOTTLENECK: {metrics.bottleneck_type}",
color=RED,
icon=WARNING)
highlight_location(metrics.bottleneck_location)
# Resource usage bars
power_bar(metrics.power_consumption, metrics.power_budget)
# Utilization indicator
draw_text(f"Utilization: {metrics.avg_utilization:.0f}%",
color=self.get_utilization_color(metrics.avg_utilization))
def get_color_for_value(self, current, target):
ratio = current / target if target > 0 else 0
if ratio >= 0.95:
return GREEN # Meeting target
elif ratio >= 0.7:
return YELLOW # Close but sub-optimal
else:
return RED # Significant underperformance
Throughput Visualization: Flow Indicators
class ConveyorBelt:
"""
Visual conveyor that shows item flow rate.
Players should SEE throughput, not just read numbers.
"""
def __init__(self, start, end, max_throughput=60):
self.start = start
self.end = end
self.max_throughput = max_throughput # items/min
self.current_throughput = 0.0
self.items_in_transit = []
# Visual feedback
self.flow_animation_speed = 1.0
self.color = WHITE
def update(self, dt):
# Calculate actual throughput
self.current_throughput = self.measure_throughput()
# Update visual feedback based on throughput
utilization = self.current_throughput / self.max_throughput
# Color coding
if utilization > 0.9:
self.color = GREEN # Running hot
elif utilization > 0.6:
self.color = YELLOW # Decent flow
elif utilization > 0.2:
self.color = ORANGE # Weak flow
else:
self.color = RED # Barely moving
# Animation speed matches throughput
self.flow_animation_speed = utilization * 2.0
# Move items along belt
for item in self.items_in_transit:
item.position += self.flow_animation_speed * dt
def render(self):
# Draw belt with color indicating flow rate
draw_line(self.start, self.end, color=self.color, width=4)
# Draw items in transit
for item in self.items_in_transit:
draw_item(item)
# Draw throughput counter
counter_position = midpoint(self.start, self.end)
draw_text(f"{self.current_throughput:.0f}/min",
position=counter_position,
background=BLACK_ALPHA,
color=self.color)
Building Status Indicators
class ProductionBuilding:
"""
Building with clear visual status indicators.
Player should instantly know if building is:
- Running optimally (GREEN)
- Input-starved (RED, no inputs available)
- Output-blocked (YELLOW, can't output products)
- Powered off (GRAY)
"""
class Status(Enum):
OPTIMAL = "OPTIMAL"
INPUT_STARVED = "INPUT_STARVED"
OUTPUT_BLOCKED = "OUTPUT_BLOCKED"
POWER_INSUFFICIENT = "POWER_INSUFFICIENT"
IDLE = "IDLE"
def __init__(self, recipe):
self.recipe = recipe
self.input_buffer = {}
self.output_buffer = {}
self.status = Status.IDLE
self.utilization = 0.0 # 0-1 ratio
def update(self, dt):
# Determine status based on state
if not self.has_power():
self.status = Status.POWER_INSUFFICIENT
self.utilization = 0.0
elif not self.has_required_inputs():
self.status = Status.INPUT_STARVED
self.utilization = 0.0
elif self.is_output_full():
self.status = Status.OUTPUT_BLOCKED
self.utilization = 0.0
else:
self.status = Status.OPTIMAL
self.utilization = 1.0
self.produce(dt)
def render(self):
# Base building sprite
draw_building(self.sprite)
# Status indicator (prominent visual)
status_color = {
Status.OPTIMAL: GREEN,
Status.INPUT_STARVED: RED,
Status.OUTPUT_BLOCKED: YELLOW,
Status.POWER_INSUFFICIENT: DARK_RED,
Status.IDLE: GRAY
}[self.status]
# Draw status ring around building
draw_ring(self.position,
radius=self.size * 1.2,
color=status_color,
width=4,
pulsate=(self.status != Status.OPTIMAL))
# Draw utilization percentage
draw_text(f"{self.utilization * 100:.0f}%",
position=self.position + Vector2(0, -20),
color=status_color)
# Draw status icon
status_icon = {
Status.OPTIMAL: ICON_CHECKMARK,
Status.INPUT_STARVED: ICON_ARROW_IN_RED,
Status.OUTPUT_BLOCKED: ICON_ARROW_OUT_YELLOW,
Status.POWER_INSUFFICIENT: ICON_LIGHTNING_OFF,
Status.IDLE: ICON_PAUSE
}[self.status]
draw_icon(status_icon, self.position + Vector2(0, 20))
Production Dashboard
class ProductionDashboard:
"""
Central analytics panel showing factory-wide performance.
Updated in real-time so players see immediate effect of changes.
"""
def __init__(self, factory):
self.factory = factory
self.history = [] # Time-series data for graphs
def update(self, dt):
# Collect current metrics
snapshot = {
'timestamp': time.now(),
'throughput': self.factory.measure_total_throughput(),
'efficiency': self.factory.calculate_efficiency(),
'bottlenecks': self.factory.identify_bottlenecks(),
'power_usage': self.factory.total_power_consumption(),
'utilization': self.factory.average_utilization()
}
self.history.append(snapshot)
# Keep last 5 minutes of history
cutoff = time.now() - timedelta(minutes=5)
self.history = [s for s in self.history if s['timestamp'] > cutoff]
def render(self):
# Throughput graph (last 5 minutes)
draw_graph(
data=[s['throughput'] for s in self.history],
title="Throughput (items/min)",
color=GREEN,
show_target=True
)
# Efficiency meter
current_efficiency = self.history[-1]['efficiency']
draw_radial_meter(
value=current_efficiency,
label="Efficiency",
color_gradient=[RED, YELLOW, GREEN]
)
# Bottleneck list (top 3 worst)
bottlenecks = self.history[-1]['bottlenecks']
draw_bottleneck_list(bottlenecks[:3])
# Resource usage bars
power_usage = self.history[-1]['power_usage']
draw_resource_bar("Power", power_usage, self.factory.power_capacity)
# Overall utilization
utilization = self.history[-1]['utilization']
draw_text(f"Factory Utilization: {utilization:.0f}%",
size=LARGE,
color=GREEN if utilization > 80 else YELLOW)
Example: Full Visibility System
class FactoryGame:
"""
Complete factory game with full metric visibility.
Every optimization decision is informed by clear data.
"""
def __init__(self):
self.buildings = []
self.conveyors = []
self.metrics = PerformanceMetrics()
self.dashboard = ProductionDashboard(self)
def update(self, dt):
# Update all production
for building in self.buildings:
building.update(dt)
for conveyor in self.conveyors:
conveyor.update(dt)
# Update metrics
self.metrics.items_per_minute = self.measure_total_throughput()
self.metrics.efficiency_percent = self.calculate_efficiency() * 100
self.metrics.bottleneck_type, self.metrics.bottleneck_location = \
self.identify_primary_bottleneck()
self.metrics.power_consumption = self.total_power_consumption()
self.metrics.footprint_size = self.calculate_footprint()
self.metrics.building_count = len(self.buildings)
self.metrics.avg_utilization = self.average_utilization() * 100
# Update dashboard
self.dashboard.update(dt)
def render(self):
# Render all buildings with status indicators
for building in self.buildings:
building.render()
# Render conveyors with flow visualization
for conveyor in self.conveyors:
conveyor.render()
# Render metrics HUD
MetricsDisplay().render(self.metrics)
# Render dashboard (if open)
if player.dashboard_open:
self.dashboard.render()
def identify_primary_bottleneck(self):
"""
Find the worst bottleneck in the factory.
Returns (type, location) tuple.
"""
worst_building = None
worst_utilization = 1.0
for building in self.buildings:
if building.utilization < worst_utilization:
worst_utilization = building.utilization
worst_building = building
if worst_building and worst_utilization < 0.9:
return (worst_building.status, worst_building.position)
else:
return (None, None)
SECTION 2: Bottleneck Identification as Core Mechanic
The Bottleneck Game
"Factory optimization is bottleneck whack-a-mole: fix one, another appears. This IS the game."
The most engaging optimization games make bottleneck discovery the primary skill.
What Makes a Good Bottleneck System
- Visible: Bottlenecks are highlighted, not hidden
- Progressive: Fixing one reveals the next
- Cascading: Bottleneck shifts as you optimize
- Multiple Types: Input-starved, output-blocked, under-powered, poor layout
- Solvable: Clear cause → clear solution
Bottleneck Detection System
class BottleneckDetector:
"""
Analyzes production chain to find weakest links.
Highlights problems for player to solve.
"""
class BottleneckType(Enum):
INPUT_STARVED = "Not enough input materials"
OUTPUT_BLOCKED = "Output buffer full, can't produce more"
THROUGHPUT_LIMITED = "Building capacity too low"
TRANSPORT_BOTTLENECK = "Conveyor can't handle flow rate"
POWER_INSUFFICIENT = "Not enough power to run at full capacity"
RATIO_IMBALANCE = "Production doesn't match consumption ratios"
def __init__(self, factory):
self.factory = factory
self.bottlenecks = []
def analyze(self):
"""
Scan entire factory for bottlenecks.
Returns list sorted by severity (worst first).
"""
self.bottlenecks = []
# Check each building
for building in self.factory.buildings:
severity = self.calculate_bottleneck_severity(building)
if severity > 0.1: # Threshold: 10% underperformance
bottleneck = {
'building': building,
'type': self.diagnose_bottleneck_type(building),
'severity': severity, # 0-1, higher = worse
'suggested_fix': self.suggest_solution(building)
}
self.bottlenecks.append(bottleneck)
# Check conveyors
for conveyor in self.factory.conveyors:
if conveyor.is_bottleneck():
self.bottlenecks.append({
'conveyor': conveyor,
'type': BottleneckType.TRANSPORT_BOTTLENECK,
'severity': conveyor.congestion_ratio(),
'suggested_fix': "Upgrade conveyor or add parallel belt"
})
# Sort by severity
self.bottlenecks.sort(key=lambda b: b['severity'], reverse=True)
return self.bottlenecks
def calculate_bottleneck_severity(self, building):
"""
How badly is this building underperforming?
0.0 = running optimally
1.0 = completely blocked/starved
"""
if building.status == Building.Status.OPTIMAL:
return 0.0
elif building.status == Building.Status.IDLE:
return 0.05 # Minor issue
else:
# Measure actual throughput vs theoretical max
actual = building.current_throughput
theoretical = building.max_throughput
return 1.0 - (actual / theoretical)
def diagnose_bottleneck_type(self, building):
"""
Determine WHY building is bottlenecked.
"""
if building.status == Building.Status.INPUT_STARVED:
return BottleneckType.INPUT_STARVED
elif building.status == Building.Status.OUTPUT_BLOCKED:
return BottleneckType.OUTPUT_BLOCKED
elif building.status == Building.Status.POWER_INSUFFICIENT:
return BottleneckType.POWER_INSUFFICIENT
elif building.current_throughput < building.max_throughput * 0.5:
return BottleneckType.THROUGHPUT_LIMITED
else:
return BottleneckType.RATIO_IMBALANCE
def suggest_solution(self, building):
"""
Give player actionable hint about how to fix bottleneck.
"""
bottleneck_type = self.diagnose_bottleneck_type(building)
suggestions = {
BottleneckType.INPUT_STARVED:
"Add more input producers or faster conveyors",
BottleneckType.OUTPUT_BLOCKED:
"Add more consumers or larger output buffers",
BottleneckType.THROUGHPUT_LIMITED:
"Build additional buildings or upgrade throughput",
BottleneckType.POWER_INSUFFICIENT:
"Build more power generators",
BottleneckType.RATIO_IMBALANCE:
f"Ratio issue: needs {building.recipe.optimal_ratio}"
}
return suggestions.get(bottleneck_type, "Optimize this building")
Visual Bottleneck Highlighting
class BottleneckVisualizer:
"""
Makes bottlenecks OBVIOUS through visual cues.
Player should instantly see problem areas.
"""
def __init__(self, detector):
self.detector = detector
def render(self):
bottlenecks = self.detector.analyze()
for bottleneck in bottlenecks[:5]: # Show top 5 worst
severity = bottleneck['severity']
if 'building' in bottleneck:
location = bottleneck['building'].position
else:
location = bottleneck['conveyor'].midpoint()
# Pulsating alert icon
self.draw_alert_marker(location, severity)
# Color-coded severity
color = self.get_severity_color(severity)
# Draw problem description
draw_tooltip(
position=location + Vector2(0, -40),
text=f"{bottleneck['type'].value}\n{bottleneck['suggested_fix']}",
background_color=color + ALPHA(0.8)
)
# Highlight affected area
draw_highlight_circle(location,
radius=50,
color=color,
pulse_speed=severity * 2)
def get_severity_color(self, severity):
"""Gradient from yellow (minor) to red (critical)"""
if severity > 0.7:
return RED
elif severity > 0.4:
return ORANGE
else:
return YELLOW
def draw_alert_marker(self, position, severity):
"""Animated warning icon that pulses based on severity"""
pulse_scale = 1.0 + math.sin(time.now() * severity * 5) * 0.2
draw_icon(ICON_WARNING,
position=position,
scale=pulse_scale,
color=self.get_severity_color(severity))
Cascading Bottleneck System
class CascadingBottlenecks:
"""
Demonstrates how fixing one bottleneck reveals the next.
This creates the core optimization gameplay loop.
"""
def __init__(self):
self.production_chain = [
# Simple chain: Miner → Smelter → Assembler
Building("Miner", max_throughput=30),
Building("Smelter", max_throughput=20), # INITIAL BOTTLENECK
Building("Assembler", max_throughput=15)
]
def simulate_optimization_progression(self):
"""
Show how bottleneck shifts as player optimizes.
"""
print("INITIAL STATE:")
self.analyze_chain()
# Output: "Bottleneck: Smelter (20/min limiting 30/min miner)"
print("\nPLAYER ACTION: Upgrade Smelter to 40/min")
self.production_chain[1].max_throughput = 40
self.analyze_chain()
# Output: "Bottleneck: Miner (30/min limiting 40/min smelter)"
# Bottleneck SHIFTED to Miner!
print("\nPLAYER ACTION: Add second Miner")
self.production_chain.insert(0, Building("Miner", max_throughput=30))
self.analyze_chain()
# Output: "Bottleneck: Assembler (15/min limiting 60/min input)"
# Now Assembler is the limit!
print("\nPLAYER ACTION: Build 4 Assemblers in parallel")
for i in range(3):
self.production_chain.append(Building("Assembler", max_throughput=15))
self.analyze_chain()
# Output: "No bottlenecks! Factory running at full capacity"
def analyze_chain(self):
throughputs = [b.max_throughput for b in self.production_chain]
bottleneck = min(throughputs)
bottleneck_building = self.production_chain[throughputs.index(bottleneck)]
print(f"Bottleneck: {bottleneck_building.name} ({bottleneck}/min)")
Example: Factorio-Style Ratio Problems
class RecipeRatios:
"""
Perfect ratio calculation - the heart of factory optimization.
Example: Iron smelting requires 3 miners per 2 smelters
"""
def __init__(self):
self.recipes = {
'iron_plate': {
'inputs': {'iron_ore': 1},
'outputs': {'iron_plate': 1},
'crafting_time': 3.5, # seconds
'producers': ['stone_furnace']
},
'gear_wheel': {
'inputs': {'iron_plate': 2},
'outputs': {'gear_wheel': 1},
'crafting_time': 0.5,
'producers': ['assembling_machine']
}
}
def calculate_optimal_ratio(self, recipe_name, target_throughput):
"""
Calculate how many buildings needed for target throughput.
Example:
- Want 60 iron plates/min
- Each furnace produces 1 plate per 3.5 seconds = 17.14/min
- Need 60 / 17.14 = 3.5 furnaces (round up to 4)
"""
recipe = self.recipes[recipe_name]
output_per_building = 60.0 / recipe['crafting_time'] # items per minute
buildings_needed = target_throughput / output_per_building
return math.ceil(buildings_needed)
def detect_ratio_imbalance(self, factory):
"""
Identify where production doesn't match consumption.
"""
imbalances = []
for resource_type in factory.all_resource_types():
production_rate = factory.measure_production(resource_type)
consumption_rate = factory.measure_consumption(resource_type)
if production_rate < consumption_rate * 0.95:
# Under-producing
imbalances.append({
'resource': resource_type,
'problem': 'UNDER_PRODUCING',
'production': production_rate,
'consumption': consumption_rate,
'deficit': consumption_rate - production_rate
})
elif production_rate > consumption_rate * 1.1:
# Over-producing (wasteful)
imbalances.append({
'resource': resource_type,
'problem': 'OVER_PRODUCING',
'production': production_rate,
'consumption': consumption_rate,
'surplus': production_rate - consumption_rate
})
return imbalances
SECTION 3: Multiple Valid Solutions (Tradeoff Design)
The Tradeoff Principle
"One correct answer = following instructions. Multiple valid approaches = optimization gameplay."
Core Tradeoff Dimensions
class OptimizationTradeoffs:
"""
Four primary dimensions players can optimize.
Different players will prioritize different dimensions.
"""
def __init__(self):
self.dimensions = {
'throughput': 0.0, # Items per minute (SPEED)
'cost': 0.0, # Resources spent (ECONOMY)
'footprint': 0.0, # Space used (COMPACTNESS)
'power': 0.0 # Power consumption (EFFICIENCY)
}
def evaluate_design(self, factory_design):
"""
Score a factory design across all dimensions.
No single "best" design - tradeoffs exist.
"""
return {
'throughput': factory_design.measure_throughput(),
'cost': factory_design.calculate_total_cost(),
'footprint': factory_design.calculate_footprint(),
'power': factory_design.total_power_consumption()
}
def compare_designs(self, design_a, design_b):
"""
Show how two designs make different tradeoffs.
Example output:
Design A: High throughput, high cost, large footprint, high power
Design B: Medium throughput, low cost, compact, low power
Winner depends on player priorities!
"""
scores_a = self.evaluate_design(design_a)
scores_b = self.evaluate_design(design_b)
comparison = {}
for dimension in self.dimensions:
if scores_a[dimension] > scores_b[dimension]:
comparison[dimension] = "A wins"
elif scores_b[dimension] > scores_a[dimension]:
comparison[dimension] = "B wins"
else:
comparison[dimension] = "Tie"
return comparison
Example: Speed vs Cost Tradeoff
class FactoryDesigns:
"""
Two valid solutions to same problem: produce 60 circuits/min.
Different players will prefer different approaches.
"""
def speed_focused_design(self):
"""
FAST but EXPENSIVE.
Use advanced buildings with high throughput.
"""
return {
'buildings': [
Building('advanced_assembler', throughput=30, cost=500, power=10),
Building('advanced_assembler', throughput=30, cost=500, power=10)
],
'total_throughput': 60,
'total_cost': 1000,
'total_power': 20,
'footprint': 4 # 2 buildings x 2 tiles each
}
def cost_focused_design(self):
"""
SLOW but CHEAP.
Use basic buildings in larger quantity.
"""
return {
'buildings': [
Building('basic_assembler', throughput=10, cost=100, power=2),
Building('basic_assembler', throughput=10, cost=100, power=2),
Building('basic_assembler', throughput=10, cost=100, power=2),
Building('basic_assembler', throughput=10, cost=100, power=2),
Building('basic_assembler', throughput=10, cost=100, power=2),
Building('basic_assembler', throughput=10, cost=100, power=2)
],
'total_throughput': 60,
'total_cost': 600,
'total_power': 12,
'footprint': 12 # 6 buildings x 2 tiles each
}
def compare(self):
"""
Speed design: 40% more expensive, 67% less power, 67% smaller footprint
Cost design: 40% cheaper, 67% more power, 3x larger footprint
Both achieve same throughput!
Player choice depends on constraints (money? space? power?)
"""
pass
Spatial Optimization: Layout Tradeoffs
class LayoutOptimization:
"""
Space is a resource. Compact layouts vs sprawling layouts.
"""
def compact_design(self):
"""
TIGHT but INFLEXIBLE.
Every tile used efficiently, but hard to expand.
"""
return {
'layout_style': 'compact',
'footprint': 20, # 4x5 grid
'conveyor_length': 15, # Short paths
'expandability': 'LOW', # No room to grow
'visual_clarity': 'LOW', # Hard to see what's happening
'throughput': 60
}
def sprawling_design(self):
"""
LOOSE but FLEXIBLE.
Easy to understand and expand, but uses more space.
"""
return {
'layout_style': 'sprawling',
'footprint': 60, # 10x6 grid
'conveyor_length': 35, # Longer paths
'expandability': 'HIGH', # Room to add more
'visual_clarity': 'HIGH', # Easy to see flow
'throughput': 60
}
def modular_design(self):
"""
MODULAR but COMPLEX.
Repeatable blueprints that tile together.
"""
return {
'layout_style': 'modular',
'footprint': 40, # 8x5 grid (4 modules of 4x2.5)
'conveyor_length': 25,
'expandability': 'MEDIUM', # Can tile modules
'visual_clarity': 'MEDIUM', # Clear within module
'throughput': 60,
'reusability': 'HIGH' # Can copy/paste module
}
Challenge System with Multiple Solutions
class OptimizationChallenge:
"""
Puzzle-style challenge with multiple valid solutions.
Players can optimize for different dimensions.
"""
def __init__(self, name, constraints):
self.name = name
self.constraints = constraints
self.leaderboards = {
'speed': [], # Fastest throughput
'cost': [], # Cheapest solution
'compact': [], # Smallest footprint
'power': [], # Lowest power usage
'balanced': [] # Best overall score
}
def evaluate_solution(self, factory):
"""
Grade solution across all dimensions.
"""
# Measure performance
throughput = factory.measure_throughput()
cost = factory.calculate_cost()
footprint = factory.calculate_footprint()
power = factory.power_consumption()
# Check constraints met
meets_constraints = (
throughput >= self.constraints['min_throughput'] and
cost <= self.constraints['max_cost'] and
footprint <= self.constraints['max_footprint'] and
power <= self.constraints['max_power']
)
if not meets_constraints:
return {'valid': False, 'reason': 'Constraints not met'}
# Calculate dimension-specific scores
scores = {
'speed_score': throughput, # Higher is better
'cost_score': 1.0 / cost, # Lower cost is better
'compact_score': 1.0 / footprint,
'power_score': 1.0 / power,
'balanced_score': self.calculate_balanced_score(
throughput, cost, footprint, power
)
}
return {'valid': True, 'scores': scores}
def calculate_balanced_score(self, throughput, cost, footprint, power):
"""
Weighted average across all dimensions.
Rewards well-rounded solutions.
"""
# Normalize each dimension to 0-1 scale
normalized = {
'throughput': throughput / self.constraints['min_throughput'],
'cost': self.constraints['max_cost'] / cost,
'footprint': self.constraints['max_footprint'] / footprint,
'power': self.constraints['max_power'] / power
}
# Equal weighting (could adjust for game balance)
weights = {'throughput': 0.4, 'cost': 0.2, 'footprint': 0.2, 'power': 0.2}
total = sum(normalized[dim] * weights[dim] for dim in weights)
return total
# Example challenge
circuit_production_challenge = OptimizationChallenge(
name="Circuit Production Challenge",
constraints={
'min_throughput': 60, # Must produce at least 60/min
'max_cost': 2000, # Can't spend more than 2000 resources
'max_footprint': 50, # Can't use more than 50 tiles
'max_power': 30 # Can't exceed 30 power
}
)
# Different players will optimize for different dimensions:
# - Speedrunner: Max throughput, ignore cost
# - Minimalist: Smallest footprint, accept lower throughput
# - Economist: Cheapest solution, maximize cost efficiency
# - Engineer: Balanced solution across all dimensions
SECTION 4: Satisfying Feedback (The Juice)
The Satisfaction Principle
"Optimization must FEEL good. Visual, audio, and numeric feedback make improvements satisfying."
Visual Feedback: Flow Animations
class FlowAnimation:
"""
Make production VISIBLE and SATISFYING to watch.
"""
def __init__(self, conveyor):
self.conveyor = conveyor
self.particles = []
def update(self, dt):
# Spawn particles based on throughput
spawn_rate = self.conveyor.current_throughput / 60.0 # per second
if random.random() < spawn_rate * dt:
self.spawn_particle()
# Move particles along belt
for particle in self.particles:
particle.position += self.conveyor.direction * dt * 100
# Arrived at destination?
if particle.position >= self.conveyor.end:
self.particles.remove(particle)
self.spawn_arrival_effect()
def spawn_particle(self):
"""Create visible item moving along belt"""
particle = {
'position': self.conveyor.start,
'sprite': self.get_item_sprite(),
'speed': 100 # pixels per second
}
self.particles.append(particle)
def spawn_arrival_effect(self):
"""Satisfying 'pop' when item reaches destination"""
spawn_particle_effect(
position=self.conveyor.end,
particle_count=5,
color=YELLOW,
lifetime=0.3
)
play_sound("item_received", volume=0.5)
def render(self):
# Draw all items in transit
for particle in self.particles:
draw_sprite(particle['sprite'], particle['position'])
Audio Feedback: Production Sounds
class ProductionAudio:
"""
Sound design that reflects factory efficiency.
Fast factory sounds busy and productive.
Slow factory sounds sluggish.
"""
def __init__(self, factory):
self.factory = factory
self.sound_pools = {
'machine_hum': AudioPool('machine_hum.wav', max_instances=10),
'conveyor_move': AudioPool('conveyor.wav', max_instances=5),
'item_process': AudioPool('process.wav', max_instances=20)
}
def update(self, dt):
# Adjust ambient factory sound based on total throughput
total_throughput = self.factory.measure_total_throughput()
ambient_intensity = min(1.0, total_throughput / 100.0)
self.sound_pools['machine_hum'].set_volume(ambient_intensity)
self.sound_pools['machine_hum'].set_pitch(0.8 + ambient_intensity * 0.4)
# Play processing sounds based on building activity
for building in self.factory.buildings:
if building.just_produced_item():
self.sound_pools['item_process'].play(
volume=building.utilization,
pitch=random.uniform(0.9, 1.1)
)
# Conveyor sounds based on flow rate
for conveyor in self.factory.conveyors:
if conveyor.current_throughput > 0:
volume = conveyor.current_throughput / conveyor.max_throughput
self.sound_pools['conveyor_move'].play(
volume=volume * 0.3,
pitch=0.8 + volume * 0.4,
loop=True
)
Milestone Celebrations
class MilestoneSystem:
"""
Celebrate achievement of production goals.
Makes optimization feel rewarding.
"""
def __init__(self):
self.milestones = [
{'threshold': 10, 'name': 'First Production', 'reward': 'Unlock automation'},
{'threshold': 60, 'name': 'One Per Second', 'reward': 'Unlock advanced buildings'},
{'threshold': 300, 'name': 'Five Per Second', 'reward': 'Unlock logistics'},
{'threshold': 1000, 'name': 'Industrial Scale', 'reward': 'Unlock megafactory tools'},
{'threshold': 6000, 'name': 'One Hundred Per Second', 'reward': 'Unlock planetary production'}
]
self.achieved = set()
def check_milestones(self, current_throughput):
"""
Check if player hit new milestone.
"""
for milestone in self.milestones:
if milestone['threshold'] not in self.achieved:
if current_throughput >= milestone['threshold']:
self.celebrate_milestone(milestone)
self.achieved.add(milestone['threshold'])
def celebrate_milestone(self, milestone):
"""
Big satisfying celebration when milestone hit.
"""
# Visual celebration
spawn_fireworks(count=20, duration=3.0)
show_banner(
text=f"MILESTONE: {milestone['name']}",
subtext=f"Producing {milestone['threshold']} items/min!",
duration=5.0,
color=GOLD
)
# Audio celebration
play_sound("milestone_fanfare")
# Reward
unlock_feature(milestone['reward'])
show_notification(f"Unlocked: {milestone['reward']}")
# Achievement tracking
save_achievement(milestone['name'], timestamp=now())
Real-Time Statistics Dashboard
class StatisticsDashboard:
"""
Detailed analytics that update in real-time.
Seeing numbers go up is satisfying.
"""
def __init__(self, factory):
self.factory = factory
self.stats = {
'items_produced_total': 0,
'items_produced_this_minute': 0,
'efficiency_current': 0.0,
'efficiency_all_time_best': 0.0,
'bottleneck_count': 0,
'uptime_percentage': 100.0
}
def update(self, dt):
# Update all statistics
self.stats['items_produced_total'] = self.factory.lifetime_production
self.stats['items_produced_this_minute'] = self.factory.recent_production
current_efficiency = self.factory.calculate_efficiency()
self.stats['efficiency_current'] = current_efficiency
if current_efficiency > self.stats['efficiency_all_time_best']:
self.stats['efficiency_all_time_best'] = current_efficiency
self.celebrate_new_record()
self.stats['bottleneck_count'] = len(self.factory.identify_bottlenecks())
self.stats['uptime_percentage'] = self.factory.calculate_uptime()
def render(self):
"""
Display statistics with emphasis on improvements.
"""
# Total production (big number = satisfying)
draw_text(f"{self.stats['items_produced_total']:,}",
size=HUGE,
color=GREEN,
label="Total Produced")
# Current throughput with sparkline
draw_metric_with_sparkline(
value=self.stats['items_produced_this_minute'],
label="Items/Min",
history=self.factory.throughput_history,
color=BLUE
)
# Efficiency with comparison to best
efficiency_pct = self.stats['efficiency_current'] * 100
best_pct = self.stats['efficiency_all_time_best'] * 100
draw_comparison_metric(
current=efficiency_pct,
best=best_pct,
label="Efficiency",
format="{:.1f}%"
)
# Bottleneck count (lower is better)
color = GREEN if self.stats['bottleneck_count'] == 0 else RED
draw_text(f"{self.stats['bottleneck_count']} Bottlenecks",
color=color)
def celebrate_new_record(self):
"""Called when player beats their efficiency record"""
show_notification("NEW RECORD EFFICIENCY!", color=GOLD)
play_sound("record_broken")
Comparison Visualization
class BeforeAfterComparison:
"""
Show player the impact of their optimization.
Visual proof of improvement is satisfying.
"""
def __init__(self):
self.snapshots = {}
def take_snapshot(self, label):
"""Capture current factory state"""
self.snapshots[label] = {
'timestamp': now(),
'throughput': factory.measure_throughput(),
'efficiency': factory.calculate_efficiency(),
'cost': factory.calculate_cost(),
'footprint': factory.calculate_footprint(),
'screenshot': factory.capture_screenshot()
}
def show_comparison(self, before_label, after_label):
"""
Side-by-side comparison showing improvement.
"""
before = self.snapshots[before_label]
after = self.snapshots[after_label]
# Visual comparison
draw_side_by_side(before['screenshot'], after['screenshot'])
# Metric improvements
improvements = {
'throughput': {
'before': before['throughput'],
'after': after['throughput'],
'change': after['throughput'] - before['throughput'],
'percent': (after['throughput'] / before['throughput'] - 1) * 100
},
'efficiency': {
'before': before['efficiency'],
'after': after['efficiency'],
'change': after['efficiency'] - before['efficiency'],
'percent': (after['efficiency'] / before['efficiency'] - 1) * 100
}
}
# Show improvements with big positive numbers
for metric, data in improvements.items():
draw_improvement_card(
metric=metric,
before=data['before'],
after=data['after'],
change=data['change'],
percent_change=data['percent']
)
# Usage
comparison = BeforeAfterComparison()
comparison.take_snapshot("initial_build")
# ... player optimizes ...
comparison.take_snapshot("after_optimization")
comparison.show_comparison("initial_build", "after_optimization")
# Shows: "Throughput improved by 150%! 🎉"
SECTION 5: Progressive Complexity
The Learning Curve Principle
"Start stupidly simple. Add complexity as player masters current level."
Tutorial Progression
class TutorialProgression:
"""
Introduce optimization concepts gradually.
Each stage builds on previous understanding.
"""
def __init__(self):
self.stages = [
self.stage_1_single_chain(),
self.stage_2_bottleneck_intro(),
self.stage_3_parallel_production(),
self.stage_4_ratios(),
self.stage_5_multi_stage(),
self.stage_6_logistics(),
self.stage_7_megafactory()
]
self.current_stage = 0
def stage_1_single_chain(self):
"""
STAGE 1: Single Production Chain
Goal: Learn basic building → conveyor → building flow
"""
return {
'name': 'Your First Factory',
'unlocked_buildings': ['miner', 'smelter', 'chest'],
'unlocked_mechanics': ['conveyors', 'placing_buildings'],
'goal': 'Produce 10 iron plates',
'complexity': 'MINIMAL',
'lesson': 'Production flows from source → processor → output',
'example_solution': lambda: [
Place('miner', (0, 0)),
Place('conveyor', (1, 0)),
Place('smelter', (2, 0)),
Place('conveyor', (3, 0)),
Place('chest', (4, 0))
]
}
def stage_2_bottleneck_intro(self):
"""
STAGE 2: First Bottleneck
Goal: Identify and fix a bottleneck
"""
return {
'name': 'Finding the Slowdown',
'unlocked_buildings': ['miner', 'smelter'],
'unlocked_mechanics': ['throughput_display', 'status_indicators'],
'starting_setup': [
# Pre-placed factory with obvious bottleneck
Building('miner', throughput=30), # Fast
Building('smelter', throughput=10) # BOTTLENECK (slow)
],
'goal': 'Increase output to 30 plates/min',
'complexity': 'LOW',
'lesson': 'Bottleneck = slowest link. Fix it to improve overall throughput',
'hint': 'The smelter can only handle 10/min but the miner produces 30/min'
}
def stage_3_parallel_production(self):
"""
STAGE 3: Parallel Production
Goal: Scale up by building in parallel
"""
return {
'name': 'Scaling Up',
'unlocked_mechanics': ['splitter', 'merger'],
'goal': 'Produce 100 iron plates/min',
'complexity': 'MEDIUM',
'lesson': 'When one building isn\'t enough, build multiple in parallel',
'example_solution': lambda: [
# 3 miners → splitter → 3 smelters (parallel processing)
Place('miner', (0, 0)),
Place('miner', (0, 1)),
Place('miner', (0, 2)),
Place('splitter_3way', (1, 1)),
Place('smelter', (2, 0)),
Place('smelter', (2, 1)),
Place('smelter', (2, 2)),
Place('merger_3way', (3, 1))
]
}
def stage_4_ratios(self):
"""
STAGE 4: Perfect Ratios
Goal: Discover optimal building ratios
"""
return {
'name': 'Perfect Balance',
'unlocked_mechanics': ['ratio_calculator'],
'goal': 'Build factory with 100% efficiency (no idle buildings)',
'complexity': 'MEDIUM',
'lesson': 'Perfect ratios mean no buildings are idle or starved',
'teaching_moment': '''
Given:
- 1 Miner produces 30 ore/min
- 1 Smelter needs 20 ore/min
Optimal ratio = 2 miners : 3 smelters (60 ore production, 60 ore consumption)
'''
}
def stage_5_multi_stage(self):
"""
STAGE 5: Multi-Stage Production
Goal: Chain multiple recipes together
"""
return {
'name': 'Production Chains',
'unlocked_buildings': ['assembler'],
'unlocked_recipes': ['gear_wheel', 'circuit'],
'goal': 'Produce 60 circuits/min (requires iron plates + copper wire)',
'complexity': 'HIGH',
'lesson': 'Complex products require managing multiple input chains',
'example_chain': lambda: {
'iron_ore': ['mine'] → ['smelt'] → ['iron_plates'],
'copper_ore': ['mine'] → ['smelt'] → ['copper_plates'] → ['wire_machine'] → ['copper_wire'],
'circuit': ['iron_plates' + 'copper_wire'] → ['assemble'] → ['circuit']
}
}
def stage_6_logistics(self):
"""
STAGE 6: Long-Distance Logistics
Goal: Transport resources across map
"""
return {
'name': 'Supply Chains',
'unlocked_buildings': ['train', 'train_station'],
'unlocked_mechanics': ['logistics_network'],
'goal': 'Transport iron from distant mine to factory',
'complexity': 'HIGH',
'lesson': 'Large-scale production requires logistics infrastructure',
'new_challenges': ['distance costs', 'train scheduling', 'supply balancing']
}
def stage_7_megafactory(self):
"""
STAGE 7: Planetary-Scale Production
Goal: Graduate to endgame complexity
"""
return {
'name': 'Megafactory',
'unlocked_buildings': ['all'],
'unlocked_mechanics': ['blueprints', 'construction_bots'],
'goal': 'Produce 6000+ science/min across 6 types',
'complexity': 'EXTREME',
'lesson': 'Megafactories require modular design and automated expansion',
'endgame_systems': ['blueprint libraries', 'city blocks', 'train grids']
}
Complexity Gating
class ComplexityGate:
"""
Unlock new mechanics only after mastering current ones.
Prevents overwhelming players.
"""
def __init__(self):
self.unlocks = {
'basic_production': {
'required_mastery': None, # Always available
'unlocks': ['miner', 'smelter', 'conveyor']
},
'bottleneck_identification': {
'required_mastery': 'basic_production',
'unlocks': ['status_indicators', 'throughput_display']
},
'parallel_scaling': {
'required_mastery': 'bottleneck_identification',
'unlocks': ['splitter', 'merger']
},
'ratio_optimization': {
'required_mastery': 'parallel_scaling',
'unlocks': ['ratio_calculator', 'efficiency_metrics']
},
'multi_stage_chains': {
'required_mastery': 'ratio_optimization',
'unlocks': ['assembler', 'complex_recipes']
},
'logistics': {
'required_mastery': 'multi_stage_chains',
'unlocks': ['trains', 'logistics_network']
},
'megafactory': {
'required_mastery': 'logistics',
'unlocks': ['blueprints', 'construction_bots', 'circuit_network']
}
}
def check_unlock(self, player_mastery):
"""
Gradually reveal complexity as player gains mastery.
"""
available = []
for system, requirements in self.unlocks.items():
if requirements['required_mastery'] is None:
available.extend(requirements['unlocks'])
elif requirements['required_mastery'] in player_mastery:
available.extend(requirements['unlocks'])
return available
Mastery Progression Example: Satisfactory
class SatisfactoryProgression:
"""
Example from Satisfactory: how complexity builds over time.
"""
def __init__(self):
self.tiers = [
{
'tier': 0,
'name': 'Hub Upgrade 1',
'available_buildings': ['miner_mk1', 'smelter', 'constructor'],
'available_recipes': ['iron_ingot', 'iron_plate', 'iron_rod'],
'complexity_rating': 1,
'player_capability': 'Can build simple linear production chains'
},
{
'tier': 2,
'name': 'Hub Upgrade 3',
'available_buildings': ['assembler', 'foundry'],
'available_recipes': ['rotor', 'modular_frame', 'steel_beam'],
'complexity_rating': 3,
'player_capability': 'Can manage 2-input recipes and steel production'
},
{
'tier': 4,
'name': 'Hub Upgrade 5',
'available_buildings': ['manufacturer', 'refinery'],
'available_recipes': ['computer', 'heavy_modular_frame', 'plastic'],
'complexity_rating': 6,
'player_capability': 'Can manage 4+ input chains and fluids'
},
{
'tier': 7,
'name': 'Hub Upgrade 8',
'available_buildings': ['particle_accelerator', 'quantum_encoder'],
'available_recipes': ['nuclear_power', 'uranium_fuel_rod', 'turbo_motor'],
'complexity_rating': 10,
'player_capability': 'Can manage planet-scale logistics with nuclear power'
}
]
def get_appropriate_challenge(self, player_tier):
"""
Match challenge complexity to player progression.
"""
tier_data = self.tiers[player_tier]
return {
'available_tools': tier_data['available_buildings'],
'recipes': tier_data['available_recipes'],
'expected_factory_scale': 10 ** (tier_data['complexity_rating'] / 3)
}
SECTION 6: Discovery Through Experimentation
The Discovery Principle
"Don't make players calculate optimal ratios. Let them discover through play."
In-Game Experimentation Tools
class ExperimentationTools:
"""
Tools that help players discover optimal solutions through play,
not spreadsheets.
"""
class RatioCalculator:
"""
In-game tool showing perfect ratios.
Removes need for external calculators.
"""
def show_recipe_requirements(self, target_recipe, target_throughput):
"""
Show player what buildings they need for desired throughput.
"""
recipe = RECIPES[target_recipe]
# Calculate buildings needed for target throughput
buildings_needed = {}
# For each input resource
for input_item, amount_needed in recipe.inputs.items():
# Find recipe that produces this input
producer_recipe = find_recipe_producing(input_item)
producer_throughput = producer_recipe.output_per_minute
# How many buildings to produce enough?
required_count = math.ceil(
(amount_needed * target_throughput) / producer_throughput
)
buildings_needed[producer_recipe.building] = required_count
# Display results
display_panel = Panel("Ratio Calculator")
display_panel.add_text(f"To produce {target_throughput} {target_recipe}/min:")
for building, count in buildings_needed.items():
display_panel.add_row(f"{count}x {building}")
return display_panel
class TestBench:
"""
Sandbox area to test designs without resource cost.
Encourages experimentation.
"""
def __init__(self):
self.test_mode = False
self.test_factory = None
def enter_test_mode(self):
"""
Free building, instant placement, time controls.
"""
self.test_mode = True
self.test_factory = Factory(mode='TEST')
# Test mode features
self.test_factory.infinite_resources = True
self.test_factory.free_buildings = True
self.test_factory.time_controls = {
'speed': 1.0, # Can speed up time to see results faster
'can_fast_forward': True,
'can_rewind': True
}
def exit_test_mode(self, save_to_blueprint=False):
"""
Exit test bench, optionally save working design.
"""
if save_to_blueprint:
self.save_as_blueprint(self.test_factory)
self.test_mode = False
class ThroughputAnalyzer:
"""
Highlight where throughput drops in production chain.
"""
def analyze_chain(self, start_building, end_building):
"""
Trace production from start to end, showing throughput at each step.
"""
chain = trace_production_chain(start_building, end_building)
for i, node in enumerate(chain):
throughput = node.measure_throughput()
max_throughput = node.max_throughput
utilization = throughput / max_throughput
# Visualize
draw_chain_node(node, utilization)
# Show throughput number
draw_text(f"{throughput:.1f}/min",
position=node.position + Vector2(0, -30))
# Highlight bottleneck
if utilization < 0.9 and i > 0:
# This node is slowest link
draw_bottleneck_highlight(node)
draw_tooltip(node.position,
f"BOTTLENECK: Only {throughput:.0f}/min (max {max_throughput:.0f}/min)")
Experimentation-Friendly Design
class ExperimentationFriendlyFactory:
"""
Design choices that encourage experimentation.
"""
def __init__(self):
# LOW COST to experiment
self.building_refund_percentage = 0.75 # Get 75% resources back when demolish
self.instant_deconstruction = True # No waiting to tear down
# FAST iteration
self.placement_speed = 'INSTANT' # No construction time in early game
self.blueprint_system = True # Copy/paste working designs
# SAFE experimentation
self.test_mode_available = True # Sandbox with no resource cost
self.undo_system = True # Can revert last 10 actions
# VISIBLE results
self.throughput_updates_per_second = 4 # See changes immediately
self.before_after_comparison = True # Can compare pre/post optimization
def encourage_iteration(self):
"""
Design systems that reward rebuilding and iteration.
"""
features = {
'refund_on_deconstruct': True, # Not punishing to tear down and rebuild
'blueprint_library': True, # Save good designs for reuse
'quick_paste': True, # Rapid placement of saved blueprints
'time_fast_forward': True, # Speed up time to see results faster
'A_B_testing': True # Build two designs side-by-side to compare
}
return features
Discovery Moments: "Aha!" Design
class DiscoveryMoments:
"""
Design for breakthrough moments where player discovers
clever optimization that dramatically improves factory.
"""
def example_discovery_1_splitter_balancing(self):
"""
DISCOVERY: Using splitters to balance load across parallel buildings.
Before (naive):
Miner → Belt → [Building 1, Building 2, Building 3]
Problem: Building 1 gets all items, others sit idle
After (discovery):
Miner → Belt → Splitter → [Building 1]
↓
Splitter → [Building 2]
↓
[Building 3]
Result: Even distribution, 3x throughput!
"""
pass
def example_discovery_2_sideloading(self):
"""
DISCOVERY: Sideloading to merge belts without splitters.
Standard: Belt1 → Merger ← Belt2 (requires merger building)
Discovery: Belt1 → ↓
Belt2 (items merge naturally)
Benefit: No building required, saves space and cost
"""
pass
def example_discovery_3_priority_splitting(self):
"""
DISCOVERY: Priority splitters to ensure critical production never starves.
Problem: Power + Consumer both need coal, but power runs out first (blackout)
Solution: Coal → Priority Splitter → Power (gets coal first)
→ Consumer (gets leftovers)
Result: Power always stable, consumer only uses excess
"""
pass
def example_discovery_4_clock_cycles(self):
"""
DISCOVERY: Using timing to create perfect ratios with different-speed buildings.
Example from SpaceChem:
- Reactor A takes 5 cycles to produce molecule
- Reactor B takes 3 cycles to process it
- Discovery: Build 3 of A and 5 of B for perfect ratio (15 cycles each)
"""
pass
def create_discoverable_mechanics(self):
"""
Design mechanics that have hidden depth.
Players discover advanced uses through experimentation.
"""
return {
'basic_use': 'Obvious, taught in tutorial',
'intermediate_use': 'Hinted at, player discovers',
'advanced_use': 'Emergent, community shares',
'expert_use': 'Unintended by designer, players innovate'
}
# Example: Conveyor Belt Mechanics (Factorio)
conveyor_belt_depth = {
'basic_use': 'Transport items from A to B',
'intermediate_use': 'Can be placed in parallel for higher throughput',
'advanced_use': 'Sideloading merges belts without splitter buildings',
'expert_use': 'Lane balancing using underground belt tricks'
}
SECTION 7: Community Competition & Sharing
The Sharing Principle
"Optimization thrives when players can compare and compete. Standardized challenges enable community."
Challenge System for Competition
class CompetitiveChallenge:
"""
Standardized scenario that players compete on.
"""
def __init__(self, name, description):
self.name = name
self.description = description
self.leaderboards = {
'speed': Leaderboard('Fastest Throughput'),
'cost': Leaderboard('Most Cost-Efficient'),
'compact': Leaderboard('Smallest Footprint'),
'power': Leaderboard('Lowest Power Usage'),
'elegant': Leaderboard('Community Votes')
}
def define_constraints(self):
"""
Fixed starting conditions ensure fair comparison.
"""
return {
'starting_resources': {'iron': 1000, 'copper': 500, 'coal': 200},
'available_buildings': ['miner', 'smelter', 'assembler', 'conveyor'],
'map_size': (20, 20), # Limited space
'power_capacity': 50, # Limited power
'objective': 'Produce 100 circuits/min',
'time_limit': None # No time pressure (optimize at leisure)
}
def submit_solution(self, player, factory):
"""
Player submits their solution for ranking.
"""
# Validate solution meets constraints
if not self.validate_solution(factory):
return {'error': 'Solution violates constraints'}
# Measure performance across all dimensions
scores = {
'throughput': factory.measure_throughput(),
'cost': factory.calculate_total_cost(),
'footprint': factory.calculate_footprint(),
'power': factory.total_power_consumption()
}
# Submit to leaderboards
for dimension, leaderboard in self.leaderboards.items():
leaderboard.submit(player, scores[dimension], factory.screenshot())
# Enable sharing
share_link = self.generate_share_link(player, factory)
return {'success': True, 'share_link': share_link, 'scores': scores}
def generate_share_link(self, player, factory):
"""
Create shareable link/code for solution.
Other players can view or copy the design.
"""
blueprint = factory.export_to_blueprint()
encoded = encode_blueprint(blueprint)
url = f"https://game.com/challenges/{self.name}/{player.id}/{encoded}"
return url
Blueprint Sharing System
class BlueprintSystem:
"""
Let players save and share factory designs.
Like sharing cookie recipes but for optimization.
"""
def __init__(self):
self.library = {}
def create_blueprint(self, factory, name, description):
"""
Save current factory design as reusable blueprint.
"""
blueprint = {
'name': name,
'description': description,
'author': current_player(),
'created_at': now(),
'buildings': [b.serialize() for b in factory.buildings],
'conveyors': [c.serialize() for c in factory.conveyors],
'performance': {
'throughput': factory.measure_throughput(),
'cost': factory.calculate_cost(),
'footprint': factory.calculate_footprint()
},
'tags': self.auto_generate_tags(factory)
}
# Generate shareable code
blueprint['share_code'] = self.encode_blueprint(blueprint)
return blueprint
def encode_blueprint(self, blueprint):
"""
Convert blueprint to compact shareable string.
Like Factorio's blueprint strings.
"""
# Serialize to JSON
json_data = json.dumps(blueprint)
# Compress
compressed = zlib.compress(json_data.encode())
# Base64 encode
encoded = base64.b64encode(compressed).decode()
return encoded
def import_blueprint(self, share_code):
"""
Import someone else's blueprint from share code.
"""
# Decode
compressed = base64.b64decode(share_code)
# Decompress
json_data = zlib.decompress(compressed).decode()
# Parse
blueprint = json.loads(json_data)
return blueprint
def paste_blueprint(self, blueprint, position):
"""
Place blueprint in world at specified position.
"""
for building_data in blueprint['buildings']:
building = Building.deserialize(building_data)
building.position += position # Offset to paste position
factory.add_building(building)
for conveyor_data in blueprint['conveyors']:
conveyor = Conveyor.deserialize(conveyor_data)
conveyor.offset_by(position)
factory.add_conveyor(conveyor)
Community Leaderboards
class Leaderboard:
"""
Competitive rankings for optimization challenges.
"""
def __init__(self, name, metric, order='DESC'):
self.name = name
self.metric = metric # 'throughput', 'cost', 'footprint', etc.
self.order = order # DESC = higher is better, ASC = lower is better
self.entries = []
def submit(self, player, score, screenshot):
"""
Add entry to leaderboard.
"""
entry = {
'player': player,
'score': score,
'screenshot': screenshot,
'timestamp': now(),
'verified': False # Anti-cheat verification pending
}
self.entries.append(entry)
self.sort_entries()
# Notify player of rank
rank = self.get_player_rank(player)
return rank
def sort_entries(self):
"""Sort by score (ascending or descending)"""
reverse = (self.order == 'DESC')
self.entries.sort(key=lambda e: e['score'], reverse=reverse)
def get_top_entries(self, count=100):
"""Get top N entries"""
return self.entries[:count]
def render_leaderboard(self):
"""
Display leaderboard with ranks, scores, and screenshots.
"""
for i, entry in enumerate(self.get_top_entries(10)):
rank = i + 1
# Medal for top 3
if rank == 1:
medal = "🥇"
elif rank == 2:
medal = "🥈"
elif rank == 3:
medal = "🥉"
else:
medal = f"#{rank}"
# Display entry
draw_leaderboard_entry(
rank=medal,
player=entry['player'].name,
score=f"{entry['score']:.1f}",
screenshot=entry['screenshot'],
clickable=True # Click to view full design
)
Example: Opus Magnum Histograms
class OpusMagnumStyleHistogram:
"""
Brilliant leaderboard design from Opus Magnum:
Shows distribution of all solutions, player sees where they rank.
"""
def __init__(self, challenge):
self.challenge = challenge
self.all_solutions = [] # Every submitted solution
def generate_pareto_frontier(self):
"""
Pareto frontier: solutions that are optimal in at least one dimension.
Example:
Solution A: Cost 50, Cycles 100, Size 10 (optimal cost)
Solution B: Cost 80, Cycles 60, Size 15 (optimal cycles)
Solution C: Cost 100, Cycles 120, Size 8 (optimal size)
Solution D: Cost 60, Cycles 110, Size 12 (NOT on frontier, A is better in all dimensions)
"""
frontier = []
for solution in self.all_solutions:
is_optimal = False
# Check if solution is best in ANY dimension
for dimension in ['cost', 'cycles', 'size']:
if solution[dimension] == min(s[dimension] for s in self.all_solutions):
is_optimal = True
break
if is_optimal:
frontier.append(solution)
return frontier
def render_histogram(self, dimension='cost'):
"""
Show histogram of all solutions for given dimension.
Player's solution highlighted.
"""
# Collect all scores for this dimension
scores = [s[dimension] for s in self.all_solutions]
# Create histogram buckets
min_score = min(scores)
max_score = max(scores)
bucket_count = 20
bucket_size = (max_score - min_score) / bucket_count
buckets = [0] * bucket_count
for score in scores:
bucket_index = int((score - min_score) / bucket_size)
bucket_index = min(bucket_index, bucket_count - 1) # Edge case
buckets[bucket_index] += 1
# Draw histogram
for i, count in enumerate(buckets):
bucket_min = min_score + i * bucket_size
bucket_max = bucket_min + bucket_size
# Draw bar
bar_height = count / max(buckets) * 100 # Normalize to 100 pixels
draw_bar(x=i * 10, height=bar_height, color=BLUE)
# Highlight player's bucket
if player_solution[dimension] >= bucket_min and player_solution[dimension] < bucket_max:
draw_bar(x=i * 10, height=bar_height, color=GOLD, highlight=True)
# Show percentile
percentile = (sum(1 for s in scores if s > player_solution[dimension]) / len(scores)) * 100
draw_text(f"You're in the top {percentile:.0f}% for {dimension}!")
SECTION 8: Real-World Implementation Examples
Example 1: Factorio's Production Lines
class FactorioProductionLine:
"""
Factorio's genius: perfect ratios create satisfying optimization.
"""
def __init__(self):
# Real Factorio recipes
self.recipes = {
'iron_plate': {
'input': ('iron_ore', 1),
'output': ('iron_plate', 1),
'time': 3.2, # seconds in stone furnace
'building': 'stone_furnace'
},
'copper_cable': {
'input': ('copper_plate', 1),
'output': ('copper_cable', 2),
'time': 0.5,
'building': 'assembling_machine'
},
'electronic_circuit': {
'input': [('iron_plate', 1), ('copper_cable', 3)],
'output': ('electronic_circuit', 1),
'time': 0.5,
'building': 'assembling_machine'
}
}
def calculate_factorio_ratios(self):
"""
Perfect ratios for circuit production:
1 Assembler (circuits) needs:
- 1 iron plate per 0.5s = 120/min
- 3 copper cable per 0.5s = 360/min
1 Assembler (copper cable) produces:
- 2 cable per 0.5s = 240/min
Therefore perfect ratio:
- 1 Copper Cable Assembler : 0.67 Circuit Assemblers
- OR: 3 Copper Cable : 2 Circuits
"""
circuit_assembler_cable_consumption = 360 # per minute
cable_assembler_production = 240 # per minute
ratio = circuit_assembler_cable_consumption / cable_assembler_production
print(f"Need {ratio:.2f} cable assemblers per circuit assembler")
# Output: "Need 1.50 cable assemblers per circuit assembler"
# In practice: 3 cable assemblers for every 2 circuit assemblers
Example 2: Opus Magnum's Optimization Puzzle
class OpusMagnumAlchemyPuzzle:
"""
Opus Magnum: Optimization puzzle with 3 scoring dimensions.
"""
def __init__(self, puzzle_name):
self.puzzle_name = puzzle_name
self.scoring_dimensions = ['cost', 'cycles', 'area']
def example_puzzle_health_potion(self):
"""
Puzzle: Create health potion from base elements.
Constraints:
- Input: 2 Fire + 1 Water + 1 Salt
- Output: 1 Health Potion
- Mechanics: Grabber arms, rotation, bonds
Three approaches:
"""
# Approach 1: SPEED OPTIMIZATION
speed_solution = {
'cost': 350, # Expensive (many arms)
'cycles': 45, # FAST (parallel processing)
'area': 25, # Large (spread out for speed)
'strategy': 'Multiple grabber arms working in parallel'
}
# Approach 2: COST OPTIMIZATION
cost_solution = {
'cost': 90, # CHEAP (minimal arms)
'cycles': 180, # Slow (serial processing)
'area': 18, # Medium
'strategy': 'Single grabber arm doing everything (slow but cheap)'
}
# Approach 3: AREA OPTIMIZATION
area_solution = {
'cost': 220, # Medium
'cycles': 95, # Medium
'area': 9, # COMPACT (everything overlapped)
'strategy': 'Clever overlapping paths to minimize footprint'
}
return [speed_solution, cost_solution, area_solution]
def score_solution(self, solution):
"""
Each dimension has its own leaderboard.
Players compete for records in each dimension.
"""
leaderboards = {
'cost': {'record': 85, 'player_score': solution['cost']},
'cycles': {'record': 42, 'player_score': solution['cycles']},
'area': {'record': 8, 'player_score': solution['area']}
}
for dimension, data in leaderboards.items():
if data['player_score'] < data['record']:
celebrate_new_record(dimension)
Example 3: Satisfactory's 3D Factory Optimization
class Satisfactory3DFactory:
"""
Satisfactory adds vertical dimension to factory optimization.
"""
def __init__(self):
self.grid = Grid3D(width=100, depth=100, height=50)
def vertical_optimization_strategies(self):
"""
3D space enables unique optimization strategies.
"""
strategies = {
'spaghetti': {
'description': 'Conveyors everywhere, no organization',
'pros': ['Fast to build', 'Works'],
'cons': ['Hard to debug', 'Ugly', 'Hard to expand'],
'visual': 'Messy but functional'
},
'city_blocks': {
'description': 'Grid of modular production cells',
'pros': ['Organized', 'Expandable', 'Debuggable'],
'cons': ['Slower to build', 'Uses more space'],
'visual': 'Clean grid pattern'
},
'vertical_layers': {
'description': 'Each production stage on different floor',
'pros': ['Clear flow', 'Easy to see throughput', 'Compact horizontally'],
'cons': ['Tall structures', 'Lots of vertical conveyors'],
'visual': 'Skyscraper factory'
},
'mall_layout': {
'description': 'Central bus of resources, production branches off',
'pros': ['Easy to add new production', 'Shared resources'],
'cons': ['Main bus can bottleneck', 'Gets wide'],
'visual': 'Highway with offramps'
}
}
return strategies
def calculate_3d_optimization(self, factory_3d):
"""
3D adds complexity: vertical conveyors, lifts, multi-floor layouts.
"""
metrics = {
'horizontal_footprint': factory_3d.calculate_2d_area(),
'vertical_footprint': factory_3d.max_height,
'total_conveyor_length': factory_3d.total_belt_length(),
'vertical_conveyor_count': factory_3d.count_vertical_lifts(),
'throughput': factory_3d.measure_throughput()
}
# Optimization: Minimize conveyor length (faster transport)
# vs Minimize footprint (compact base)
# vs Minimize height (easier to navigate)
return metrics
Example 4: SpaceChem's Constraint Optimization
class SpaceChemReactor:
"""
SpaceChem: Optimization under strict constraints.
"""
def __init__(self):
self.grid_size = (10, 8) # Fixed size reactor
self.max_symbols = None # Unlimited instructions
def example_puzzle_methane_synthesis(self):
"""
Puzzle: Synthesize methane (CH4) from C and H atoms.
Constraints:
- 10x8 grid (fixed size)
- 2 waldos (robot arms) per reactor
- Instructions: grab, drop, bond, rotate, sync
Optimization dimensions:
- Cycles: How fast does it produce one molecule?
- Symbols: How many instructions used? (code golf)
- Reactors: How many reactors needed? (less is better)
"""
solution_simple = {
'cycles': 95,
'symbols': 34,
'reactors': 1,
'description': 'Straightforward solution, no tricks'
}
solution_optimized_cycles = {
'cycles': 52,
'symbols': 48,
'reactors': 1,
'description': 'Waldos work in parallel, clever synchronization'
}
solution_optimized_symbols = {
'cycles': 120,
'symbols': 18,
'description': 'Minimal instructions, reuse paths, slower but elegant'
}
return [solution_simple, solution_optimized_cycles, solution_optimized_symbols]
def scoring_system(self):
"""
SpaceChem's brilliant scoring:
- Histograms show distribution of all solutions
- Player sees percentile ranking
- Pareto frontier shows optimal solutions
- Community competitions for lowest cycles/symbols
"""
return {
'primary_metric': 'Puzzle completed (yes/no)',
'secondary_metrics': ['Cycles', 'Symbols', 'Reactors'],
'presentation': 'Histogram + Pareto frontier',
'social': 'View friends\' solutions, compete for records'
}
Example 5: Dyson Sphere Program's Planetary Logistics
class DysonSphereProgramLogistics:
"""
DSP: Planet-scale and interstellar logistics optimization.
"""
def __init__(self):
self.planets = []
self.interstellar_logistics = []
def planetary_production(self):
"""
Each planet specializes in different resources.
"""
planets = {
'starter_planet': {
'resources': ['iron', 'copper', 'coal'],
'production': 'Basic materials',
'exports': 'Iron plates, copper wire',
'imports': 'Advanced components'
},
'oil_planet': {
'resources': ['oil'],
'production': 'Plastic, organic crystals',
'exports': 'Plastic, graphene',
'imports': 'None (self-sufficient)'
},
'silicon_planet': {
'resources': ['silicon'],
'production': 'Processors, quantum chips',
'exports': 'High-tech components',
'imports': 'Copper wire, plastic'
}
}
return planets
def interstellar_optimization(self):
"""
Optimize logistics across star systems.
Challenges:
- Travel time (minutes between planets)
- Vessel capacity (how much to carry)
- Fuel costs (warp fuel consumption)
- Throughput (items per minute across vast distances)
"""
optimization_strategies = {
'local_production': {
'description': 'Produce everything locally on each planet',
'pros': ['No logistics complexity', 'Self-sufficient'],
'cons': ['Inefficient', 'Duplicate infrastructure']
},
'specialized_planets': {
'description': 'Each planet specializes, ships between them',
'pros': ['Efficient resource use', 'Interesting optimization'],
'cons': ['Complex logistics', 'Bottlenecks from shipping']
},
'hub_and_spoke': {
'description': 'One hub planet, spokes gather resources',
'pros': ['Centralized production', 'Easy to optimize'],
'cons': ['Hub can bottleneck', 'Long shipping routes']
}
}
return optimization_strategies
SECTION 9: Common Pitfalls & Solutions
Pitfall 1: Hidden Math (Spreadsheet Required)
Problem: Players need Excel to calculate optimal ratios.
# BAD: Requires external calculation
class HiddenMathFactory:
"""
Player must manually calculate:
- Building A produces 7.2 items per second
- Building B consumes 3.1 items per second
- Ratio = 7.2 / 3.1 = 2.32...
- Need 23 of A and 10 of B for perfect ratio
This is HOMEWORK, not gameplay.
"""
pass
# GOOD: In-game tools show ratios
class TransparentMathFactory:
"""
Hover over building → tooltip shows:
"Produces 7.2/sec iron plates"
"Perfect ratio: 23 miners → 10 smelters"
Player learns through UI, not spreadsheets.
"""
def show_ratio_tooltip(self, building):
recipe = building.recipe
# Calculate optimal ratio to other buildings
ratios = calculate_optimal_ratios(recipe)
tooltip = f"{building.name}\n"
tooltip += f"Produces: {recipe.output_rate:.1f}/sec\n"
tooltip += f"Perfect ratio:\n"
for other_building, ratio in ratios.items():
tooltip += f" {ratio:.1f}x {other_building}\n"
return tooltip
Solution: In-game ratio calculator, tooltips showing perfect ratios, visual indicators when ratio is off.
Pitfall 2: One Optimal Solution
Problem: Every player builds identical factory because only one design works.
# BAD: Single optimal solution
class SingleSolutionPuzzle:
"""
Only one way to achieve target throughput.
Player looks up solution online, copies it.
No creativity, no optimization gameplay.
"""
def __init__(self):
self.target = 100 # items/min
self.only_valid_design = [
Building('miner', position=(0, 0)),
Building('miner', position=(1, 0)),
Building('smelter', position=(2, 0)),
# ... exact layout required ...
]
# GOOD: Multiple valid approaches
class MultiSolutionPuzzle:
"""
Many ways to achieve target, each with tradeoffs.
"""
def __init__(self):
self.target = 100
self.constraints = {
'max_cost': 1000, # But not too strict
'max_footprint': 50,
'max_power': 30
}
# Players can optimize for speed, cost, OR space
# Multiple valid solutions exist
Solution: Design challenges with multiple optimization dimensions. Tradeoffs ensure no single best solution.
Pitfall 3: No Visible Feedback
Problem: Can't see why factory is slow or what to improve.
# BAD: Invisible problems
class InvisibleBottlenecks:
"""
Factory is slow but player has no idea why.
No indicators, no metrics, just frustration.
"""
def update(self):
# Factory runs but nothing tells player about problems
for building in self.buildings:
building.update() # Silently starved or blocked
# GOOD: Clear visual feedback
class VisibleBottlenecks:
"""
Player instantly sees problems:
- Red buildings are input-starved
- Yellow buildings are output-blocked
- Green buildings are running optimally
- Numbers show exact throughput
"""
def render(self):
for building in self.buildings:
# Status color
color = {
'STARVED': RED,
'BLOCKED': YELLOW,
'OPTIMAL': GREEN
}[building.status]
# Draw with status indicator
building.render_with_status(color)
# Show throughput
draw_text(f"{building.throughput:.0f}/min", building.position)
Solution: Rich visual feedback, status indicators, throughput numbers, bottleneck highlighting.
Pitfall 4: Overwhelming Complexity at Start
Problem: Tutorial dumps 20 building types and complex ratios immediately.
# BAD: Everything at once
class OverwhelmingTutorial:
"""
Tutorial: "Here are 20 building types, 15 resource types,
power system, fluids, trains, and circuits. Good luck!"
Result: Player quits, overwhelmed.
"""
def tutorial(self):
unlock_buildings(['miner', 'smelter', 'assembler', 'chemical_plant',
'refinery', 'rocket_silo', ...]) # TOO MUCH
# GOOD: Progressive revelation
class ProgressiveTutorial:
"""
Stage 1: Just miner + smelter
Stage 2: Add assembler
Stage 3: Add parallel production
Stage 4: Add logistics
...
Each stage builds on previous mastery.
"""
def tutorial_stage_1(self):
unlock_buildings(['miner', 'smelter'])
goal = "Produce 10 iron plates"
# Master this first, then move on
Solution: Progressive complexity gating. Unlock new mechanics only after mastering current ones.
Pitfall 5: No Stakes (Optimization Doesn't Matter)
Problem: Players can ignore optimization and still progress fine.
# BAD: Optimization is optional busywork
class NoStakesGame:
"""
Inefficient factory works just as well as optimized one.
No motivation to improve.
"""
def progress_gate(self, factory):
# Gate only checks if factory EXISTS, not if it's good
if factory.has_any_production():
unlock_next_level() # Optimization didn't matter
# GOOD: Optimization has intrinsic rewards
class IntrinsicRewardsGame:
"""
Optimization is NOT required, but it's satisfying:
- Visual satisfaction (smooth flow)
- Competitive leaderboards
- Personal challenge (beat your own record)
- Efficiency achievements
"""
def provide_intrinsic_motivation(self):
return {
'leaderboards': 'Compare with friends',
'achievements': 'Personal mastery goals',
'satisfaction': 'Watching efficient factory run',
'creativity': 'Express personal style',
'community': 'Share clever designs'
}
# NOT: "You must optimize to progress"
# YES: "Optimizing feels good"
Solution: Make optimization intrinsically rewarding (satisfying visuals, community competition, personal achievement), not extrinsically required (gates blocking progress).
SECTION 10: Testing & Validation Checklist
Pre-Launch Checklist
class OptimizationGameplayChecklist:
"""
Validate that optimization is actually fun.
"""
def __init__(self):
self.checks = []
def visibility_checks(self):
"""Can players SEE performance?"""
return [
("Throughput displayed in real-time", self.check_throughput_display()),
("Bottlenecks are visually highlighted", self.check_bottleneck_visualization()),
("Efficiency percentage shown", self.check_efficiency_metrics()),
("Status indicators on all buildings", self.check_status_indicators()),
("Dashboard with detailed analytics", self.check_dashboard_exists())
]
def feedback_loop_checks(self):
"""Is iteration fast and satisfying?"""
return [
("Changes reflect in < 1 second", self.check_update_speed()),
("Before/after comparison available", self.check_comparison_tool()),
("Milestones celebrate improvements", self.check_celebration_system()),
("Audio feedback reflects throughput", self.check_audio_scaling()),
("Visual flow animations", self.check_flow_visualization())
]
def tradeoff_checks(self):
"""Are there multiple valid solutions?"""
return [
("Speed vs Cost tradeoffs exist", self.check_tradeoffs('speed', 'cost')),
("Compact vs Sprawling both viable", self.check_tradeoffs('footprint', 'flexibility')),
("High-power vs Low-power options", self.check_tradeoffs('power', 'throughput')),
("At least 3 valid approaches per challenge", self.check_solution_diversity()),
("No single 'best' solution", self.check_pareto_frontier())
]
def discovery_checks(self):
"""Can players discover through play?"""
return [
("Ratio calculator in-game", self.check_ratio_tools()),
("Test bench for experimentation", self.check_sandbox_mode()),
("Refund on deconstruction", self.check_refund_system()),
("Blueprint save/load", self.check_blueprint_system()),
("Tutorial teaches discovery, not recipes", self.check_tutorial_style())
]
def progression_checks(self):
"""Does complexity build gradually?"""
return [
("First puzzle is trivial (< 5 min)", self.check_first_puzzle_simplicity()),
("Mechanics unlocked progressively", self.check_unlock_pacing()),
("Each stage requires mastery of previous", self.check_mastery_gates()),
("Endgame complexity >> starting complexity", self.check_depth_curve()),
("No overwhelming info dumps", self.check_tutorial_pacing())
]
def community_checks(self):
"""Can players share and compete?"""
return [
("Challenge system with leaderboards", self.check_leaderboard_exists()),
("Blueprint sharing implemented", self.check_sharing_system()),
("Multiple leaderboard dimensions", self.check_leaderboard_dimensions()),
("Screenshot/video export", self.check_export_tools()),
("Community can upvote designs", self.check_voting_system())
]
def satisfaction_checks(self):
"""Does optimization FEEL good?"""
return [
("Numbers going up is visible", self.check_visible_progress()),
("Smooth animations for production", self.check_animation_quality()),
("Satisfying audio for efficiency", self.check_audio_juice()),
("Celebration for milestones", self.check_celebration_moments()),
("Pride in showing off factory", self.check_shareability())
]
def run_all_checks(self):
"""
Run complete validation suite.
"""
all_checks = (
self.visibility_checks() +
self.feedback_loop_checks() +
self.tradeoff_checks() +
self.discovery_checks() +
self.progression_checks() +
self.community_checks() +
self.satisfaction_checks()
)
passed = sum(1 for _, check in all_checks if check)
total = len(all_checks)
print(f"Optimization Gameplay Validation: {passed}/{total} checks passed")
if passed < total * 0.8:
print("WARNING: Optimization may not be engaging enough")
return passed / total
Playtesting Metrics
class OptimizationPlaytestMetrics:
"""
Measure whether players actually enjoy optimizing.
"""
def __init__(self):
self.metrics = {}
def measure_engagement(self, playtest_session):
"""
Track behavioral indicators of engagement.
"""
return {
'time_spent_optimizing': playtest_session.time_in_optimization_mode,
'factories_rebuilt': playtest_session.count_rebuilds,
'aha_moments': playtest_session.count_verbal_eureka(),
'voluntary_optimization': playtest_session.optimized_without_prompt,
'blueprint_usage': playtest_session.blueprints_created,
'repeated_play': playtest_session.returned_to_same_challenge
}
def measure_satisfaction(self, playtest_session):
"""
Qualitative measures of satisfaction.
"""
return {
'verbal_positive': playtest_session.count_positive_exclamations(),
'leaned_forward': playtest_session.body_language_engagement,
'showed_friend': playtest_session.unprompted_sharing,
'requested_more': playtest_session.asked_for_next_challenge,
'post_survey_rating': playtest_session.survey_score()
}
def red_flags(self, playtest_session):
"""
Warning signs that optimization isn't fun.
"""
warnings = []
if playtest_session.opened_external_calculator:
warnings.append("Player needed external tool (provide in-game calculator)")
if playtest_session.time_idle > playtest_session.time_building:
warnings.append("More waiting than doing (speed up feedback loop)")
if playtest_session.asked_for_optimal_solution:
warnings.append("Player wants answer, not discovery (improve discovery tools)")
if playtest_session.verbal_frustration > 3:
warnings.append("High frustration (unclear feedback or too difficult)")
if playtest_session.quit_before_completion:
warnings.append("Abandoned puzzle (check difficulty curve)")
return warnings
SECTION 11: Decision Framework
When to Use Optimization-as-Play
class DecisionFramework:
"""
Help designers decide if optimization-as-play fits their game.
"""
def should_use_optimization_as_play(self, game_design):
"""
Decision tree for choosing this design pattern.
"""
# REQUIRED prerequisites
if not game_design.has_systemic_production():
return False, "No production systems to optimize"
if not game_design.has_measurable_metrics():
return False, "Can't optimize what you can't measure"
# STRONG indicators this will work
positive_signals = [
game_design.players_enjoy_efficiency,
game_design.multiple_solutions_possible,
game_design.bottlenecks_are_natural,
game_design.mastery_curve_exists,
game_design.community_competition_viable
]
# WARNING signs this might not work
negative_signals = [
game_design.action_focused, # Real-time action != optimization
game_design.narrative_heavy, # Story pacing != optimization pacing
game_design.only_one_solution, # No room for creativity
game_design.instant_gratification_required, # Optimization takes time
game_design.metrics_feel_artificial # Must be natural to game
]
score = sum(positive_signals) - sum(negative_signals)
if score >= 3:
return True, "Strong fit for optimization-as-play"
elif score >= 1:
return True, "Could work with careful design"
else:
return False, "Optimization might feel tacked-on"
def alternatives_if_not_suitable(self):
"""
If optimization-as-play isn't right, what else?
"""
return {
'action_games': 'Use execution-based challenges (combat, platforming)',
'narrative_games': 'Use story choices and character progression',
'puzzle_games': 'Use fixed-solution puzzles (no optimization axis)',
'exploration_games': 'Use discovery and collection as rewards'
}
Hybrid Approaches
class HybridOptimization:
"""
Optimization-as-play can be PART of game, not the whole game.
"""
def example_mindustry(self):
"""
Mindustry: Tower defense + factory optimization hybrid.
"""
return {
'primary_gameplay': 'Tower defense (action)',
'secondary_gameplay': 'Factory optimization (strategy)',
'integration': 'Better factory → better towers → easier defense',
'time_split': '60% action, 40% optimization',
'works_because': 'Optimization happens between waves, not during'
}
def example_oxygen_not_included(self):
"""
Oxygen Not Included: Survival + resource optimization.
"""
return {
'primary_gameplay': 'Colony survival (crisis management)',
'secondary_gameplay': 'Resource optimization (efficiency)',
'integration': 'Efficient systems free up time to explore',
'time_split': '50% crisis response, 50% optimization',
'works_because': 'Optimization reduces future crises'
}
def example_cracktorio_drug_production(self):
"""
Hypothetical: Factorio-like illegal drug production sim.
"""
return {
'primary_gameplay': 'Factory optimization (production chains)',
'secondary_gameplay': 'Risk management (police, competitors)',
'integration': 'Efficient production → more profit → better defenses',
'twist': 'Optimization under pressure (police raids interrupt production)',
'works_because': 'Optimization is central but not the only challenge'
}
SECTION 12: Conclusion & Summary
Core Principles Recap
class OptimizationAsPlayPrinciples:
"""
The 10 commandments of optimization-as-play design.
"""
def __init__(self):
self.principles = {
1: "Make metrics VISIBLE (throughput, efficiency, bottlenecks)",
2: "Identify bottlenecks as CORE MECHANIC (whack-a-mole gameplay)",
3: "Design TRADEOFFS (speed vs cost vs space vs power)",
4: "Make optimization SATISFYING (juice, celebration, feedback)",
5: "Progressive COMPLEXITY (simple → complex as player learns)",
6: "Enable DISCOVERY (experimentation, not calculation)",
7: "Support COMMUNITY (sharing, competition, leaderboards)",
8: "Provide TOOLS (ratio calculator, test bench, blueprints)",
9: "Ensure MULTIPLE SOLUTIONS (no single 'best' answer)",
10: "Make it OPTIONAL but IRRESISTIBLE (intrinsic motivation)"
}
def the_golden_rule(self):
"""
If you remember only one thing:
"""
return "Optimization must be THE GAME, not a chore you do to play the game."
The Optimization Gameplay Loop (Final Form)
OBSERVE
↓
Factory running, metrics displayed
↓
IDENTIFY
↓
Bottleneck highlighted (RED building)
↓
HYPOTHESIZE
↓
"If I add another smelter, throughput should increase"
↓
EXPERIMENT
↓
Build smelter, place it (instant feedback)
↓
MEASURE
↓
Throughput increased! (satisfying animation, numbers go up)
↓
DISCOVER
↓
"Wait, now the CONVEYOR is the bottleneck!" (cascade)
↓
ITERATE
↓
(loop continues, always finding next optimization)
Success Criteria
Your optimization-as-play implementation succeeds when:
- Players spend hours optimizing for fun, not because they're forced
- Multiple solutions exist for every challenge (community discovers new approaches)
- Bottleneck discovery is engaging (detective work, not frustration)
- Metrics are clear (no external tools needed)
- Improvements feel satisfying (juice, celebration, visible progress)
- Complexity builds gradually (tutorial → mastery curve)
- Community thrives (sharing blueprints, competing on leaderboards)
- Discovery happens (players find clever solutions you didn't anticipate)
- Tradeoffs are meaningful (speed vs cost vs space, all valid)
- Players show off factories (pride in their creations)
Final Code Example: Complete System
class CompleteOptimizationGame:
"""
Putting it all together: a complete optimization-as-play system.
"""
def __init__(self):
# Core systems
self.factory = Factory()
self.metrics = PerformanceMetrics()
self.bottleneck_detector = BottleneckDetector(self.factory)
self.visualizer = BottleneckVisualizer(self.bottleneck_detector)
# Player tools
self.ratio_calculator = RatioCalculator()
self.test_bench = TestBench()
self.blueprint_system = BlueprintSystem()
# Progression
self.tutorial = TutorialProgression()
self.challenges = ChallengeSystem()
self.milestones = MilestoneSystem()
# Community
self.leaderboards = LeaderboardSystem()
self.sharing = SharingSystem()
# Feedback
self.audio = ProductionAudio(self.factory)
self.celebrations = CelebrationSystem()
def update(self, dt):
"""Main game loop"""
# Update production
self.factory.update(dt)
# Update metrics (real-time feedback)
self.metrics.items_per_minute = self.factory.measure_throughput()
self.metrics.efficiency_percent = self.factory.calculate_efficiency() * 100
# Detect bottlenecks (core mechanic)
bottlenecks = self.bottleneck_detector.analyze()
if bottlenecks:
self.metrics.bottleneck_type = bottlenecks[0]['type']
self.metrics.bottleneck_location = bottlenecks[0]['location']
# Update audio (satisfaction)
self.audio.update(dt)
# Check milestones (celebration)
self.milestones.check_milestones(self.metrics.items_per_minute)
def render(self):
"""Visual feedback"""
# Render factory with status indicators
self.factory.render()
# Highlight bottlenecks
self.visualizer.render()
# Display metrics HUD
MetricsDisplay().render(self.metrics)
def player_opens_ratio_calculator(self, recipe):
"""In-game tool replaces spreadsheets"""
return self.ratio_calculator.show_recipe_requirements(recipe, target_throughput=100)
def player_enters_test_mode(self):
"""Experimentation without cost"""
self.test_bench.enter_test_mode()
def player_submits_challenge_solution(self, challenge_id):
"""Community competition"""
challenge = self.challenges.get_challenge(challenge_id)
result = challenge.submit_solution(current_player(), self.factory)
return result # Contains rank, share link, scores
def player_shares_blueprint(self, name, description):
"""Community sharing"""
blueprint = self.blueprint_system.create_blueprint(
self.factory, name, description
)
share_code = blueprint['share_code']
return share_code # Can be posted online, imported by others
# Usage
game = CompleteOptimizationGame()
# Game loop
while running:
game.update(dt)
game.render()
handle_input()
# Result: Optimization IS the game
Implementation Checklist
- Visible metrics (throughput, efficiency, bottlenecks)
- Status indicators on all buildings
- Bottleneck detection system
- Visual bottleneck highlighting
- Multiple optimization dimensions (speed/cost/space/power)
- Tradeoff design (no single best solution)
- Satisfying visual feedback (flow animations)
- Satisfying audio feedback (production sounds)
- Milestone celebration system
- Real-time statistics dashboard
- Progressive complexity (tutorial → endgame)
- Complexity gating (unlock gradually)
- In-game ratio calculator
- Test bench / sandbox mode
- Blueprint system (save/load designs)
- Refund on deconstruction (encourage iteration)
- Challenge system
- Leaderboards (multiple dimensions)
- Sharing system (blueprint codes)
- Before/after comparison tool
This skill document provides production-ready patterns for making optimization itself the core gameplay loop. The key insight is that optimization becomes play when it's visible, satisfying, and opens up creative expression rather than following predetermined solutions.
Go build factories that players can't stop optimizing.