Skip to content

Latest commit

 

History

History
1632 lines (1171 loc) · 53.9 KB

File metadata and controls

1632 lines (1171 loc) · 53.9 KB

GenAI Usage Documentation — Boids Interactive Simulation

Author: Kimon Anagnostopoulos
Date: January 2026
AI Assistant: Claude (Anthropic)


Project Overview

This document records the complete development process of a Boids flocking simulation, from core algorithm implementation to an interactive web application with real-time parameter tuning. Every prompt, issue, test result, and design decision is documented for reproducibility.

The project consists of two major phases:

  1. Part I: Core Algorithm — Implementing the Boids flocking rules, KD-Tree optimization, predator-prey dynamics, and quantitative analysis
  2. Part II: Interactive Web Application — Building a FastAPI/WebSocket backend and React frontend for real-time visualization

Prompting Strategy

The implementation follows an incremental, test-driven approach:

  1. Decomposition: Break the simulation into self-contained modules
  2. Test-first approach: Each module includes unit tests before integration
  3. Iterative refinement: Evaluate generated code against specifications, request corrections as needed
  4. Documentation: Record every interaction for reproducibility

Workflow Pattern: Plan-and-Solve with Test-Driven Development

  1. Established complete implementation plan before coding (Steps 1-10 + Tiers)
  2. For each step: define goal → write tests → implement → verify → document
  3. User testing after integration to catch behavioral issues not covered by unit tests
  4. Iterative parameter tuning based on visual observation

Prompt Structure

Each implementation prompt followed this pattern:

  • Context: "We are building a Boids flocking simulation..."
  • Specific task: Clear, bounded objective (e.g., "Implement the separation rule")
  • Constraints: Technical requirements
  • Quality expectations: Edge cases, test coverage requirements

Key Decisions Made During Development

Decision Point Choice Made Rationale
Data structure @dataclass Minimal boilerplate, clear intent
Coordinate system Screen coords (y-down) Standard for pygame/canvas
Rules as functions Pure functions in rules.py Testable, composable
Parameter storage SimulationParams dataclass Centralized, documentable
Visualization Pygame (standalone) / Canvas (web) Platform-appropriate
Testing framework pytest Industry standard, clear syntax
Web framework FastAPI + WebSocket Async, high performance
Frontend React + TypeScript Type safety, component model

Project Status

Final State

Component Status Tests
Part I: Core Algorithm
Boid class ✅ Complete 13
Separation rule ✅ Complete 8
Alignment rule ✅ Complete 7
Cohesion rule ✅ Complete 6
Rules integration ✅ Complete 2
Flock class ✅ Complete 22
Visualization (Pygame) ✅ Complete Smoke tested
Parameter tuning ✅ Complete N/A
KDTree optimization (Tier 1) ✅ Complete 13
Predator avoidance (Tier 2) ✅ Complete 38
Quantitative analysis (Tier 3) ✅ Complete 35
Part II: Interactive Demo
Project structure ✅ Complete
Configuration & validation ✅ Complete 35
Models & serialization ✅ Complete 36
Simulation manager ✅ Complete 33
Presets system ✅ Complete 22
WebSocket server ✅ Complete 15
Frontend (React) ✅ Complete
Static obstacles ✅ Complete 34
Multiple predators ✅ Complete 23
Predator species/strategies ✅ Complete 23
Boundary regression fix ✅ Complete 19
Hunting improvements ✅ Complete 34
Docker containerization ✅ Complete

Part I Tests: 144 passing
Part II Tests: 357 passing
Total Tests: 501 passing

Files Structure

boids-interactive/
├── README.md                    # Project documentation
├── GENAI_USAGE.md              # This file
├── docker-compose.yml          # Container orchestration
│
├── backend/
│   ├── Dockerfile
│   ├── main.py                 # FastAPI WebSocket server
│   ├── simulation_manager.py   # Simulation lifecycle
│   ├── config.py               # Parameters & validation
│   ├── models.py               # Pydantic models
│   ├── presets.py              # Preset configurations
│   ├── requirements.txt
│   │
│   ├── boids/                  # Core simulation engine
│   │   ├── boid.py             # Boid data structure
│   │   ├── predator.py         # Predator with hunting strategies
│   │   ├── flock.py            # Basic flock manager
│   │   ├── flock_optimized.py  # KDTree-optimized flock
│   │   ├── rules.py            # Flocking rules (naive)
│   │   ├── rules_optimized.py  # KDTree-based rules
│   │   ├── obstacle.py         # Static obstacles
│   │   └── metrics.py          # Analysis metrics
│   │
│   ├── tests/                  # Test suite
│   ├── analysis.py             # Parameter sweep (Tier 3)
│   ├── benchmark.py            # Performance comparison
│   └── visualization.py        # Pygame visualization
│
├── frontend/
│   ├── Dockerfile
│   ├── nginx.conf
│   ├── package.json
│   └── src/
│       └── App.tsx             # React application
│
└── docs/
    ├── boids_specification.tex # LaTeX specification
    └── CONTAINERIZATION_PLAN.md

Part I: Core Algorithm Implementation

This section documents the development of the core Boids simulation engine with full detail on prompts, issues, and iterations.


Step 1: Boid Class (Data Structure)

Goal: Create a minimal Boid class that stores position (x, y) and velocity (vx, vy).

Constraints:

  • Simple data structure, no methods yet
  • Should support easy instantiation with random or specified values
  • Use standard Python with NumPy for vector operations where beneficial

Prompt: "Implement a Boid class as a data structure storing position and velocity. Include a factory method for random initialization within given bounds."

Code produced: boid.py

Tests: test_boid.py

*Evaluation:

  • Boid instantiates with explicit position/velocity
  • Boid instantiates with random values within bounds
  • Attributes are accessible and modifiable
  • Edge case: zero velocity handled

Test results: 13/13 tests passed

Issues found: None.

Design decisions:

  • Used @dataclass for clean, minimal boilerplate
  • Added speed, position, and velocity properties for convenience
  • Factory method create_random() encapsulates random initialization
  • Random velocity uses angle-based generation for uniform direction distribution

Status: Complete


Step 2: Separation Rule

Goal: Implement the separation behavior; boids steer away from neighbors within protected range.

Constraints:

  • Match the algorithm from specification
  • Input: current boid, list of all boids, protected range, strength
  • Output: velocity adjustment tuple (dvx, dvy)
  • Handle edge case: no neighbors in protected range

Prompt: "Implement separation, alignment, and cohesion rules in a single module. Each rule should be a pure function returning velocity adjustments. Separation uses protected range only; alignment and cohesion use visual range but exclude boids in protected range (per the pseudocode structure)."

Code produced: rules.pycompute_separation() function

Tests: 8 tests covering:

  • No neighbors → zero adjustment
  • Neighbor outside protected range → zero adjustment
  • Single neighbor in range → correct repulsion direction
  • Multiple symmetric neighbors → forces cancel
  • Asymmetric neighbors → net force
  • Strength factor scaling
  • Diagonal neighbor → both x and y components

Evaluation:

  • Matches specification algorithm exactly
  • Uses squared distance to avoid unnecessary sqrt
  • Accumulates displacement vectors from all intruders
  • Applies strength factor correctly

Test results: 8/8 passed

Status: Complete


Step 3: Alignment Rule

Goal: Implement velocity matching with visible neighbors.

Constraints:

  • Boids in protected range are excluded
  • Only boids in visual range (but outside protected range) contribute
  • Returns velocity adjustment toward average neighbor velocity

Code produced: rules.pycompute_alignment() function

Tests: 7 tests covering:

  • No neighbors → zero adjustment
  • Neighbor in protected range → excluded
  • Neighbor outside visual range → excluded
  • Same velocity → zero adjustment
  • Different velocity → steer toward neighbor
  • Matching factor scaling
  • Opposing velocities → average to zero

Evaluation:

  • Correctly excludes boids in protected range
  • Computes average velocity of valid neighbors
  • Applies matching factor as (avg - current) * factor

Test results: 7/7 passed

Status: Complete


Step 4: Cohesion Rule

Goal: Implement steering toward center of mass of visible neighbors.

Constraints:

  • Same visibility rules as alignment (exclude protected range)
  • Steer toward center of mass, not individual neighbors

Code produced: rules.pycompute_cohesion() function

Tests: 6 tests covering:

  • No neighbors → zero adjustment
  • Neighbor in protected range → excluded
  • Single neighbor → steer toward neighbor position
  • Centering factor scaling
  • Multiple neighbors → steer toward center of mass
  • Equilateral triangle → boid at centroid has zero net force

Evaluation:

  • Correctly computes center of mass
  • Steering is (center - position) * factor
  • Symmetric arrangements produce balanced forces

Test results: 6/6 passed

Additional integration tests: 2 tests verifying correct interaction between rules (protected range exclusion behavior)

Total for Steps 2-4: 23/23 tests passed

Status: Complete


Step 5: Combined Update Loop

Goal: Integrate all three rules into a single update step per boid.

Constraints:

  • Apply rules in order: separation, alignment, cohesion
  • Add boundary steering
  • Enforce speed limits after all adjustments
  • Update position last

Prompt: "Create a Flock class with SimulationParams dataclass. Implement the combined update loop that applies all rules, boundary steering, speed limits, and position updates in the correct order."

Code produced: flock.pyFlock.update_boid() method

Design decisions:

  • Created SimulationParams dataclass to encapsulate all tunable parameters
  • Flock manages list of boids and provides update methods
  • Sequential update (noted as simplification vs. parallel update)

Test results: See Step 8 summary

Status: Complete


Step 6: Boundary Handling

Goal: Implement edge avoidance with turn factor.

Constraints:

  • Screen coordinates: (0,0) top-left, y increases downward
  • Soft margins: gradual steering, not hard teleportation
  • Each edge handled independently

Code produced: flock.pyFlock.apply_boundary_steering() method

Tests: 7 tests covering:

  • No steering in center
  • Left/right/top/bottom margin steering directions
  • Corner steering (both dimensions)
  • Turn factor magnitude

Evaluation:

  • Correct coordinate system (y increases downward)
  • Independent handling of each margin
  • Steering direction pushes boid toward center

Test results: 7/7 passed

Status: Complete


Step 7: Speed Limits

Goal: Enforce minimum and maximum speed constraints.

Constraints:

  • Preserve velocity direction when clamping
  • Handle edge case: zero speed

Code produced: flock.pyFlock.enforce_speed_limits() method

Tests: 5 tests covering:

  • Speed within limits unchanged
  • Speed above max clamped
  • Speed below min boosted
  • Direction preserved when clamped
  • Zero speed handled (random direction at min_speed)

Evaluation:

  • Direction preservation via unit vector scaling
  • Zero speed edge case handled with random direction

Test results: 5/5 passed

Status: Complete


Step 8: Position Update

Goal: Update boid positions based on velocity.

Implementation: Simple Euler integration: position += velocity

Code produced: Integrated into Flock.update_boid() method

Tests for full integration (Steps 5-8):

  • 3 tests for flock initialization
  • 7 tests for boundary handling
  • 5 tests for speed limits
  • 3 tests for single boid update
  • 2 tests for full flock update
  • 2 tests for helper methods

Total for Steps 5-8: 22/22 tests passed

Cumulative test count: 58/58 passed

Status: Complete


Step 9: Visualization (Pygame)

Goal: Render boids and animate the simulation.

Constraints:

  • Smooth 60fps animation
  • Boids rendered as triangles pointing in velocity direction
  • Dark background for visibility
  • FPS counter for debugging
  • Keyboard controls (ESC to quit, R to reset)

Prompt: "Create pygame visualization with triangular boids oriented by velocity. Include headless mode for testing and benchmarking. Add keyboard controls."

Code produced: visualization.py and main.py

Features implemented:

  • draw_boid(): Renders boid as oriented triangle
  • run_simulation(): Main pygame loop with event handling
  • run_headless(): Non-visual simulation for testing
  • Command-line argument for boid count
  • FPS display toggle

Smoke test: Headless simulation with 20 boids runs 100 steps successfully

Evaluation:

  • Boids rendered as triangles
  • Orientation matches velocity direction
  • ESC and R keyboard controls
  • FPS counter displayed
  • Headless mode for testing

Note: Full visual testing requires running on a system with display. Headless mode verified programmatically.

Status: Complete


Step 10: Parameter Tuning

Goal: Adjust parameters for realistic flocking behavior.

Step 10a: Cornell Parameter Configuration

Problem identified: Initial parameters caused:

  • Sharp 180° turns at screen edges (turn_factor too high)
  • Multiple separate flocks forming (visual_range too large relative to cohesion)
  • Boids too fast for smooth animation

Reference: Cornell University Boids parameter recommendations

Changes made:

Parameter Before After Rationale
turn_factor 0.5 0.2 Gentler edge turns
visual_range 75 20 Tighter local neighborhoods
protected_range 12 2 Smaller personal space
cohesion_factor 0.005 0.0005 10x reduction for stability
max_speed 6.0 3.0 Slower, smoother motion
margin 100 50 Smaller boundary zone

Files modified: flock.py, main.py

Test impact: 9 tests failed due to hardcoded assumptions about defaults. Fixed by adding explicit SimulationParams to affected tests.

Test results: 58/58 passed after fixes

Verification: User should run python main.py to verify:

  • Smoother edge behavior (no sharp 180° turns)
  • Flocks merge more naturally
  • Overall motion appears more realistic

User testing results (Step 10a):

  • ✓ Movement is smooth
  • ✓ Turns feel natural
  • ✗ Boids leave screen boundaries
  • ✗ Boids completely dispersed (singles or small groups)
  • ✗ Boids violate each other's protected range when together

Step 10b: Tuning for Cohesion and Boundaries

Problem analysis:

  1. Boids leave screen: margin=50 + turn_factor=0.2 insufficient to turn boid at max_speed=3 before exit. At speed 3, turn_factor 0.2 takes ~15 frames to reverse = 45px traveled.
  2. Dispersed boids: visual_range=20 too small for 800×600 screen. cohesion_factor=0.0005 extremely weak (neighbor 15px away pulls at only 0.0075 px/frame). Random starts never coalesce.
  3. Protected range violation: protected_range=2 means boids must be within 2px to trigger separation. separation_strength=0.05 too weak to overcome momentum.

Changes made:

Parameter Step 10a Step 10b Rationale
visual_range 20 40 2x increase to find neighbors
protected_range 2 8 4x increase for earlier avoidance
cohesion_factor 0.0005 0.002 4x increase to pull boids together
separation_strength 0.05 0.1 2x increase to enforce spacing
margin 50 75 1.5x increase for earlier boundary turns

Unchanged (working well):

  • turn_factor: 0.2 (natural turns confirmed)
  • alignment_factor: 0.05
  • max_speed: 3.0, min_speed: 2.0

Files modified: flock.py, main.py

Test results: 58/58 passed

Verification: User should test for:

  • Boids stay within screen boundaries
  • Boids form cohesive flocks (not dispersed) — takes ~30 seconds
  • Boids maintain spacing (no protected range violations)
  • Motion still smooth, turns still natural

User testing results (Step 10b):

  • ✓ Boids stay within screen boundaries
  • ✓ Boids form cohesive flock after ~30 seconds
  • ✓ Motion is smooth
  • ✓ Turns feel natural
  • ✗ Jittery motion when boids get very close (touching)
  • ✗ Flock separates at abrupt turns (potential visual_range / alignment issue)

User feedback: "After 30 seconds or so the boids form a flock. The motions seems to be a bit jittery as they get very close to each other and get within each other's protected range. This probably implies the protected range rules need to be a bit stricter. Also, the flock separated at an abrupt term. Is that to be expected in terms of emergent behavior?"

Analysis:

  1. Jittery motion / touching: protected_range=8 triggers separation too late; separation_strength=0.1 insufficient to overcome momentum when boids are already close.
  2. Flock separation at turns: Partially emergent behavior (real flocks do split/reform), but also indicates visual_range=40 may be too small — boids at flock edge lose sight of others during turns. alignment_factor=0.05 may be too weak for synchronized direction changes.

Step 10c: Fix Jittery Motion and Flock Cohesion

Goal: Eliminate jittery motion from protected range violations and improve flock cohesion during turns.

User applied changes manually.

Changes applied:

Parameter Step 10b Step 10c Rationale
protected_range 8 12 Earlier separation trigger
separation_strength 0.1 0.15 Stronger push to prevent touching
visual_range 40 50 Maintain cohesion during turns
alignment_factor 0.05 0.06 Smoother coordinated turns

Unchanged (working well):

  • cohesion_factor: 0.002
  • turn_factor: 0.2
  • margin: 75
  • max_speed/min_speed: 3.0/2.0

User testing results (Step 10c):

  • ✓ Behavior is satisfactory and seems natural
  • ✓ Jittery motion resolved
  • ⚠ Minor issue: Some boids occasionally go slightly out of screen bounds
  • Decision: Defer boundary fine-tuning to interactive tool (Tier 3)

Test impact: 2 tests in user's local copy failed due to outdated test file not having explicit SimulationParams. Updated test_flock.py provided with explicit parameters.

Test results: 58/58 passed (with updated test file)

Status: Complete


Step 10d: Edge Case Verification

Goal: Verify all edge cases are handled correctly per specification.

Edge cases verified through unit tests:

  • Zero neighbors (isolated boid) — tested in rules tests
  • Single neighbor scenarios — tested in rules tests
  • Boid at exact screen edge — boundary steering tests
  • Boid at corner (two boundaries) — corner steering test
  • Zero velocity — handled with random direction at min_speed
  • Speed clamping — preserves direction

Status: Complete (covered by existing 58 tests)


Step 10e: Final Behavioral Validation

Goal: Confirm simulation meets requirements.

Validation criteria (from specification):

  • Initial conditions: Random positions/velocities → gradual flock formation (~30s)
  • Separation rule: Minimum spacing maintained after Step 10c fixes
  • Alignment rule: Boids tend to move in similar directions
  • Cohesion rule: Boids cluster together rather than disperse
  • Emergent behavior: Natural flocking patterns observed
  • Smooth motion and natural turns at boundaries

User confirmation: "The behavior is satisfactory and seems natural."

Status: Complete


Parameter Evolution Table

Parameter Initial 10a (Cornell) 10b (Cohesion fix) 10c (Final)
visual_range 75 20 40 50
protected_range 12 2 8 12
cohesion_factor 0.005 0.0005 0.002 0.002
alignment_factor 0.05 0.05 0.05 0.06
separation_strength 0.05 0.05 0.1 0.15
max_speed 6.0 3.0 3.0 3.0
min_speed 2.0 2.0 2.0 2.0
turn_factor 0.5 0.2 0.2 0.2
margin 100 50 75 75

Observations per iteration:

  • Initial: Sharp 180° turns, multiple flocks, too fast
  • 10a: Smooth turns, but dispersed boids, leave screen
  • 10b: Forms flock in ~30s, but jittery when close, splits on turns
  • 10c: Natural behavior, satisfactory flocking ✓

Core Assignment Complete

All Phase 1-3 requirements satisfied:

  • Phase 1: Conceptual understanding documented in LaTeX
  • Phase 2: Implementation complete with test-driven development
  • Phase 3: Behavioral validation and parameter exploration complete

Proceeding to Enhancement Tiers for additional points.


Tier 1: KDTree Optimization

Goal: Replace O(n²) naive neighbor-finding with spatial indexing for performance.


T1.1: Implement KDTree Neighbor Finding

Prompt: "Create optimized rules module using scipy.spatial.KDTree for neighbor queries. Maintain identical interface to naive implementation. Include FlockState class to manage spatial index."

Code produced:

  • rules_optimized.py — KDTree-based rules with FlockState class
  • flock_optimized.pyFlockOptimized class using KDTree

Design decisions:

  • FlockState class encapsulates KDTree and position/velocity arrays
  • Tree rebuilt each frame (positions change constantly)
  • compute_all_rules_kdtree() combines all three rules with shared queries for efficiency
  • FlockOptimized uses parallel update semantics (compute all adjustments, then apply)

Key implementation details:

# Query neighbors once for each range
visual_neighbors = set(flock_state.query_neighbors(boid_index, visual_range))
protected_neighbors = set(flock_state.query_neighbors(boid_index, protected_range))
# Neighbors for alignment/cohesion (in visual but outside protected)
flocking_neighbors = visual_neighbors - protected_neighbors

T1.2: Verify Identical Behavior

Tests implemented: test_optimization.py

Test categories:

  1. FlockState tests (4 tests)

    • Empty flock handling
    • Single boid handling
    • Neighbor query correctness
    • Tree rebuild on update
  2. Rules equivalence tests (4 tests)

    • Separation: KDTree matches naive exactly
    • Alignment: KDTree matches naive exactly
    • Cohesion: KDTree matches naive exactly
    • Combined rules: KDTree matches sum of naive rules
  3. Flock equivalence tests (2 tests)

    • Single step produces similar positions
    • Long run stability (500 steps)

Test results: 13/13 passed

Note on equivalence: Naive Flock uses sequential updates (each boid sees partially-updated state of previous boids), while FlockOptimized uses parallel semantics (all boids computed from same initial state, then all updated). This is actually more correct for physical simulation but produces slightly different trajectories.


T1.3: Performance Benchmarks

Benchmark methodology:

  • Warm-up: 10 frames discarded
  • Timed run: 100 frames averaged
  • Same random seed for fair comparison
  • Tests: 50, 100, 150, 200, 300, 400, 500 boids

Results:

Boids Naive (ms) KDTree (ms) Speedup
50 2.28 1.19 1.92×
100 8.74 2.48 3.53×
150 19.82 3.92 5.05×
200 34.45 5.44 6.33×
300 77.38 8.92 8.67×
400 135.64 12.75 10.64×
500 212.84 17.19 12.38×

Analysis:

  • Naive implementation shows O(n²) growth: doubling boids roughly quadruples time
  • KDTree implementation shows O(n log n) growth: much slower increase
  • At 500 boids, KDTree is 12.4× faster
  • KDTree enables 60fps with 500+ boids (17ms < 16.67ms threshold)
  • Naive can only maintain 60fps with ~50 boids

Figure generated: benchmark_results.png


T1.4: Documentation of Issues Encountered

Issue 1: Protected range exclusion

  • Initial implementation forgot to exclude protected-range boids from cohesion/alignment
  • Caught by equivalence tests comparing against naive implementation
  • Fixed by using set subtraction: visual_neighbors - protected_neighbors

Issue 2: Sequential vs parallel update semantics

  • Naive Flock.update() modifies boids sequentially
  • KDTree version computed all adjustments from stale positions
  • Resolution: Documented as intentional difference (parallel is more physically accurate)
  • Equivalence test relaxed to check "similar" not "identical" positions

Issue 3: Empty flock edge case

  • Initial FlockState crashed on empty boid list
  • Fixed by checking len(self.boids) == 0 before building KDTree

Tier 1 Files Added

boids/
├── rules_optimized.py    # KDTree-based flocking rules
├── flock_optimized.py    # FlockOptimized class
├── test_optimization.py  # Equivalence and benchmark tests
└── benchmark.py          # Performance visualization script

Tier 1 Status: Complete


Tier 2: Predator Avoidance

Goal: Introduce antagonistic agent that disrupts flocking behavior.

Specification Requirements:

  • Implement a Predator class with position and velocity
  • Add a fourth rule (predator avoidance): boids should steer away from the predator with high priority
  • The predator should move toward the center of the flock or track the nearest boid
  • Demonstrate that flocking behavior is disrupted when predator is present

T2.0: Planning and Design

Design Decisions

Decision Options Choice Rationale
Detection range Same as visual_range / Larger 100 (2×) Boids should detect predator from further away
Avoidance priority Additive / Override Weighted additive Spec says "override cohesion if necessary"
Predator hunting Nearest boid / Flock center Center More realistic hunting behavior
Predator speed Slower / Same / Faster 2.5 (slower) If faster, boids can never escape
Visualization Color / Shape / Size Larger red triangle Must be visually distinct

Edge Cases to Handle

Edge Case Expected Behavior Test Strategy
Predator at exact same position as boid Avoid division by zero; random direction Unit test
All boids equidistant from predator Each boid moves directly away Unit test
Predator outside detection range No avoidance applied Unit test
Single boid vs predator Boid flees directly away Unit test
Boid between predator and wall Boid slides along wall Integration test
Predator at screen edge Predator steered back like boids Unit test
No boids (empty flock) Predator stays stationary Unit test
Predator disabled mid-simulation Flock should gradually reform Behavioral test

T2.1: Predator Class Implementation

Prompt: "Create a Predator class with position, velocity, and hunting behavior. Include methods for tracking flock center of mass and nearest boid. Handle boundary steering and speed limits like boids."

Code produced: predator.py

Features implemented:

  • Predator dataclass with x, y, vx, vy
  • Factory methods: create_at_position(), create_random()
  • Flock tracking: compute_flock_center(), compute_nearest_boid()
  • Steering: steer_toward(), update_velocity_toward_center(), update_velocity_toward_nearest()
  • Boundary handling: same logic as boids
  • Speed limits: configurable max/min

Tests: 27/27 passed

Status: Complete


T2.2: Predator Avoidance Rule

Prompt: "Implement fourth rule for predator avoidance. Avoidance should scale inversely with distance. Handle edge case of predator at exact same position as boid."

Code produced:

  • rules.py: compute_predator_avoidance()
  • rules_optimized.py: compute_predator_avoidance_kdtree(), compute_all_rules_with_predator_kdtree()

Key design decisions:

  • Avoidance scales with (detection_range - distance) / detection_range
  • Closer predator = stronger avoidance (inverse relationship)
  • Zero-distance edge case: random direction with strong magnitude
  • Detection range (100px) is 2× visual range for earlier warning

Avoidance formula:

scale = (detection_range - distance) / detection_range
dvx = (dx / distance) * avoidance_strength * scale * detection_range

Tests: 11 additional tests (38/38 total in test_predator.py)

Status: Complete


T2.3: Predator Parameters in SimulationParams

Parameters added:

Parameter Default Value Description
predator_detection_range 100 2× visual_range for early detection
predator_avoidance_strength 0.5 Strong but not overwhelming
predator_speed 2.5 Slightly slower than boids (3.0)
predator_hunting_strength 0.05 How aggressively predator tracks flock

Status: Complete


T2.4: Integration into Flock Classes

Changes to Flock class:

  • Added predator: Optional[Predator] attribute
  • Added enable_predator parameter to __init__
  • Modified update_boid() to include predator avoidance
  • Added update_predator() method
  • Added toggle_predator() method for runtime control

Changes to FlockOptimized class:

  • Same changes as Flock class
  • Uses compute_all_rules_with_predator_kdtree() for efficiency

Tests: All existing tests still pass (109/109)

Status: Complete


T2.5: Visualization Updates

Changes to visualization.py:

  • Added RED and YELLOW colors for predator
  • Added draw_predator() function (larger triangle with outline)
  • Updated run_simulation() to render predator
  • Added predator status display in UI

Visual design:

  • Predator: Larger red triangle (size=15 vs boid size=8)
  • Yellow outline for emphasis
  • Status text shows "Predator: ON/OFF"

Status: Complete


T2.6: Keyboard Controls

Added controls:

  • P: Toggle predator on/off
  • R: Reset simulation (preserves predator state)

Command line:

python main.py 50 --predator  # Start with predator enabled
python visualization.py 100 --predator --naive  # Naive mode with predator

Status: Complete


T2.7: Behavioral Validation

User testing results:

  • Flock disperses when predator approaches
  • Flock reforms when predator moves away / is disabled
  • Predator successfully tracks flock center
  • Boids flee in correct direction (away from predator)
  • No performance regression (maintains 60fps)
  • Toggle (P key) works correctly during simulation

User feedback: "This looks pretty good for now. Behavior is as expected."

Tier 2 Status: Complete


Tier 3: Quantitative Analysis

Goal: Systematic measurement of predator-prey dynamics.

Specification Requirements:

  • Track at least two metrics: average distance to predator, minimum distance to predator
  • Optionally track flock cohesion (standard deviation of positions)
  • Run parameter sweep varying two parameters (3-5 values each)
  • Run 5+ repetitions per parameter combination (different seeds)
  • Compute mean and standard deviation of metrics
  • Generate contour plot or heatmap visualization

T3.0: Planning and Design

Metrics to Track

Metric Formula Interpretation
Avg distance to predator mean(dist(boid, predator)) Higher = better escape
Min distance to predator min(dist(boid, predator)) Higher = safer (no close calls)
Flock cohesion std(boid positions) Lower = tighter flock

Parameter Sweep Design

Primary sweep: Predator speed vs Avoidance strength

Parameter Values Rationale
predator_speed [1.5, 2.0, 2.5, 3.0, 3.5] Range from slower to faster than boids
predator_avoidance_strength [0.1, 0.3, 0.5, 0.7, 0.9] Weak to strong avoidance

Grid: 5 × 5 = 25 parameter combinations
Repetitions: 5 runs per combination with different seeds
Total runs: 125 simulations
Frames per run: 500 (about 8 seconds at 60fps)

Potential Issues

  1. Long runtime: 125 runs × 500 frames could be slow

    • Mitigation: Use headless mode, KDTree optimization
  2. Statistical noise: Stochastic initialization means high variance

    • Mitigation: 5+ repetitions, report mean ± std
  3. Metric interpretation: What does "better" mean?

    • Higher avg distance = boids escape well
    • But: very high avoidance might fragment flock permanently
  4. Edge cases in metrics:

    • Empty flock: division by zero
    • Predator disabled: infinite distance
    • Mitigation: Only track when predator is active

T3.1: Metric Tracking Implementation

Prompt: "Implement metric functions for quantitative analysis: distance to predator, flock cohesion. Include MetricsCollector class for frame-by-frame data collection."

Code produced: metrics.py

Functions implemented:

  • compute_distance_to_predator() — Single boid distance
  • compute_avg_distance_to_predator() — Mean across all boids
  • compute_min_distance_to_predator() — Closest approach
  • compute_flock_cohesion() — Std dev of positions
  • compute_flock_spread() — Max pairwise distance
  • MetricsCollector class — Frame-by-frame collection
  • run_simulation_with_metrics() — Complete run helper

Tests: 26/26 passed (test_metrics.py)

Status: Complete


T3.2: Parameter Sweep Infrastructure

Prompt: "Create parameter sweep runner that varies predator speed and avoidance strength, runs multiple repetitions, and aggregates results."

Code produced: analysis.py

Classes and functions:

  • ExperimentConfig — Sweep configuration dataclass
  • ExperimentResults — Results storage with mean/std
  • run_single_experiment() — Single parameter combination
  • run_parameter_sweep() — Full grid sweep with progress

Experiment design:

Setting Value
Predator speeds [1.5, 2.0, 2.5, 3.0, 3.5]
Avoidance strengths [0.1, 0.3, 0.5, 0.7, 0.9]
Grid size 5 × 5 = 25 combinations
Repetitions 5 per combination
Total runs 125 simulations
Frames per run 500

Tests: 9/9 passed (test_analysis.py)

Status: Complete


T3.3: Visualization — Heatmap

Functions implemented:

  • create_heatmap() — Single metric heatmap
  • create_combined_figure() — All three metrics side-by-side
  • print_results_table() — Console output

Visualization features:

  • Heatmap with viridis colormap
  • Contour lines with labels
  • Proper axis labels and colorbar
  • Combined 3-panel figure

Status: Complete


T3.4: Experiment Results

Experiment executed: 125 runs in 100.0 seconds

Results — Mean Average Distance to Predator (pixels):

str=0.1 str=0.3 str=0.5 str=0.7 str=0.9
speed=1.5 256.0 224.2 241.1 226.5 254.5
speed=2.0 230.4 232.2 225.2 237.5 231.2
speed=2.5 222.1 241.8 227.2 219.3 241.4
speed=3.0 219.3 224.5 234.8 235.9 228.6
speed=3.5 221.9 233.4 235.7 233.3 228.3

Results — Mean Minimum Distance to Predator (pixels):

str=0.1 str=0.3 str=0.5 str=0.7 str=0.9
speed=1.5 109.5 105.1 107.6 108.9 112.7
speed=2.0 103.6 112.5 107.1 111.6 106.0
speed=2.5 103.4 110.3 106.0 105.8 113.5
speed=3.0 101.7 106.1 105.7 110.4 108.0
speed=3.5 99.8 107.8 113.5 109.5 107.4

Analysis:

  1. Average distance is relatively stable across parameter combinations (220-256 px), suggesting the simulation reaches a dynamic equilibrium.
  2. Minimum distance decreases with predator speed (top-left to bottom-left column): faster predators get closer to boids on average. This confirms predator speed affects threat level.
  3. Avoidance strength effect is subtle — no strong monotonic trend. This may indicate:
    • The default detection range (100px) is sufficient for escape regardless of avoidance strength
    • Cohesion and other forces balance out extreme avoidance
    • The flock's collective behavior provides "safety in numbers"
  4. Interesting outlier: speed=3.5, str=0.1 has lowest min distance (99.8), confirming fast predator + weak avoidance is most dangerous.

Figure generated: parameter_sweep_results.png

Tier 3 Status: Complete


Part I Summary

Implementation complete. All tiers implemented and tested:

Component Status Tests
Core simulation Complete 58
KDTree optimization (Tier 1) Complete 13
Predator avoidance (Tier 2) Complete 38
Quantitative analysis (Tier 3) Complete 35

Total Part I Tests: 144/144 passing

Part II: Interactive Web Application

This section documents the development of the real-time web interface built on top of the core algorithm.


Step 1: Project Structure Setup

Prompt: "Create the project directory structure for an interactive boids demo with FastAPI backend and React frontend. Include GENAI_USAGE.md to track development."

Actions taken:

  • Created directory structure
  • Copied existing boids simulation code to backend/boids/
  • Created backend/boids/__init__.py with package exports
  • Created this documentation file

Directory structure created:

boids-interactive/
├── GENAI_USAGE.md           # This file
├── backend/
│   ├── boids/               # Copied from existing project
│   │   ├── __init__.py
│   │   ├── boid.py
│   │   ├── predator.py
│   │   ├── flock.py
│   │   ├── flock_optimized.py
│   │   ├── rules.py
│   │   ├── rules_optimized.py
│   │   └── metrics.py
│   └── tests/
└── frontend/
    ├── public/
    └── src/

Files created:

File Lines Description
backend/boids/__init__.py 28 Package exports
GENAI_USAGE.md This documentation

Status: Complete


Step 2: Requirements

Prompt: "Create requirements.txt with dependencies for FastAPI, WebSocket support, Pydantic validation, and testing."

Actions taken:

  • Created requirements.txt with all dependencies
  • Fixed relative imports in boids package (changed from boid import to from .boid import)
  • Verified all imports work
  • Verified simulation runs correctly

Dependencies:

fastapi>=0.109.0
uvicorn[standard]>=0.27.0
websockets>=12.0
pydantic>=2.0.0
numpy>=1.24.0
scipy>=1.11.0
pytest>=7.4.0
pytest-asyncio>=0.21.0
httpx>=0.25.0

Status: Complete


Step 3: Configuration System

Prompt: "Create a configuration module with SimulationConfig class that has all simulation parameters with validation, range checking, and serialization methods."

Code produced: config.py

Features:

  • SimulationConfig class with all parameters
  • Range validation for each parameter
  • update() method for partial updates with validation
  • to_dict() / from_dict() serialization
  • Constants for parameter ranges (MIN/MAX values)

Parameter ranges:

Parameter Min Max Default
num_boids 1 200 50
visual_range 10 150 50
protected_range 5 50 20
separation_strength 0.01 0.5 0.15
cohesion_factor 0.0001 0.02 0.002
alignment_factor 0.01 0.2 0.06
max_speed 1 8 3.0
min_speed 0 3 0.0
predator_speed 0.5 5 2.5
predator_avoidance_strength 0.05 1.5 0.8
num_predators 1 5 1

Tests: 35/35 passed

Status: Complete


Step 4: Pydantic Models

Prompt: "Create Pydantic models for all WebSocket message types. Include server-to-client messages (frame, params_sync, error) and client-to-server messages (update_params, pause, resume, reset, preset)."

Code produced: models.py

Message types defined:

Type Direction Purpose
FrameMessage Server→Client Simulation state at 60 FPS
ParamsSyncMessage Server→Client Current parameter values
ErrorMessage Server→Client Error notifications
UpdateParamsMessage Client→Server Parameter changes
ControlMessage Client→Server pause/resume/reset
PresetMessage Client→Server Load named preset
AddObstacleMessage Client→Server Add obstacle at position
ClearObstaclesMessage Client→Server Remove all obstacles

Frame message structure:

class FrameMessage(BaseModel):
    type: Literal["frame"] = "frame"
    frame_id: int
    boids: List[List[float]]  # [[x, y, vx, vy], ...]
    predators: List[PredatorData]
    obstacles: List[List[float]]  # [[x, y, radius], ...]
    metrics: MetricsData

Tests: 36/36 passed

Status: Complete


Step 5: Simulation Manager

Prompt: "Create SimulationManager class that owns the FlockOptimized instance, handles the frame loop, and provides methods for parameter updates, preset loading, and obstacle management."

Code produced: simulation_manager.py

Features:

  • Owns FlockOptimized instance
  • Async start() / stop() methods for frame loop
  • get_frame() returns current state as FrameMessage
  • update_params() with validation and hot-swapping
  • load_preset() applies named preset
  • add_obstacle() / remove_obstacle() / clear_obstacles()
  • pause() / resume() / reset()
  • Frame rate targeting (default 60 FPS)

Key implementation detail:

async def _frame_loop(self):
    while self._running:
        if not self._paused:
            self._flock.update()
            self._frame_id += 1
        await asyncio.sleep(1.0 / self._target_fps)

Tests: 33/33 passed

Status: Complete


Step 6: Presets System

Prompt: "Create a presets module with pre-configured parameter combinations for interesting flocking behaviors. Include presets for tight swarm, loose cloud, high speed, predator chase, etc."

Code produced: presets.py

Presets defined:

Preset Key Characteristics
default Balanced flocking behavior
tight_swarm High cohesion (0.01), low separation (0.05)
loose_cloud Low cohesion (0.0005), high separation (0.3)
high_speed max_speed=6, min_speed=3
slow_dance max_speed=1.5, high alignment (0.15)
predator_chase Predator enabled, high avoidance (1.2)
swarm_defense 3 predators, maximum avoidance (1.5)

Tests: 22/22 passed

Status: Complete


Step 7: WebSocket Server

Prompt: "Create FastAPI application with WebSocket endpoint that streams simulation frames and handles client commands. Include REST endpoints for health check and service info."

Code produced: main.py

Endpoints:

  • GET / — Service info JSON
  • GET /health — Health check for Docker
  • WS /ws — WebSocket connection

WebSocket protocol:

  1. Client connects
  2. Server creates SimulationManager for this connection
  3. Server sends params_sync with current state
  4. Server starts frame loop, sends frame messages at 60 FPS
  5. Client sends commands (update_params, pause, preset, etc.)
  6. Server responds with updated frames
  7. On disconnect, server stops simulation and cleans up

Error handling:

  • Invalid JSON → error message sent
  • Unknown message type → error message sent
  • Parameter validation failure → error message with details
  • Connection errors → graceful cleanup

Tests: 15/15 passed

Status: Complete


Step 8: Frontend Setup

Prompt: "Initialize React frontend with TypeScript and Vite. Create basic App component with WebSocket connection hook and Canvas rendering."

Actions taken:

  • Created Vite project with React + TypeScript template
  • Set up WebSocket connection management
  • Created Canvas rendering component
  • Added basic UI controls

Initial issues encountered:

  • WebSocket connection failed initially (CORS)
  • Fixed by ensuring backend allows all origins in development

Status: Complete


Step 9: WebSocket Debugging

Issue: WebSocket connections were dropping unexpectedly.

Root cause: Race condition between frame loop starting and client being ready to receive.

Solution: Added handshake - server waits for client "ready" message before starting frame loop.

Status: Resolved


Step 10: Enhanced Visuals

Prompt: "Improve the frontend visualization with better boid rendering, predator colors by strategy, and optional motion trails."

Features added:

  • Boids rendered as triangles with velocity-based orientation
  • Predators colored by hunting strategy:
    • Hawk: Red (#ff4444)
    • Falcon: Orange (#ff8800)
    • Eagle: Yellow (#ffcc00)
    • Kite: Green (#44ff44)
    • Osprey: Blue (#4488ff)
  • Optional motion trails
  • Dark background (#1a1a2e)
  • FPS counter display

Status: Complete


Step 11: Static Obstacles

Prompt: "Add support for static circular obstacles that boids navigate around. Include obstacle avoidance rule, WebSocket commands for adding/removing obstacles, and Canvas rendering."

Backend changes:

  • Created obstacle.py with Obstacle class
  • Added compute_obstacle_avoidance() rule
  • Integrated into FlockOptimized.update()
  • Added obstacle management to SimulationManager
  • Added WebSocket message types for obstacles

Frontend changes:

  • Added obstacle rendering (gray circles)
  • Added click-to-add-obstacle interaction
  • Added clear obstacles button

Tests: 34/34 passed

Status: Complete


Step 12: Multiple Predators with Hunting Strategies

Goal: Support up to 5 predators with unique hunting behaviors.

Step 12.1-12.6: Multi-Predator Infrastructure

Changes:

  • Modified Flock and FlockOptimized to support List[Predator]
  • Added num_predators parameter
  • Each predator assigned unique strategy on creation
  • Boids avoid all predators (sum of avoidance forces)

Tests: 23/23 passed


Step 12.7: Predator Species (Hunting Strategies)

Prompt: "Implement 5 distinct hunting strategies for predators: Hawk (center), Falcon (nearest), Eagle (stragglers), Kite (patrol), Osprey (random)."

Strategies implemented:

Strategy Name Target Selection Behavior
0 Hawk Flock centroid Causes flock to split
1 Falcon Nearest boid Creates localized panic
2 Eagle Farthest from center Picks off stragglers
3 Kite Patrol waypoints Circular patrol pattern
4 Osprey Random boid Unpredictable switching

Implementation:

class HuntingStrategy(Enum):
    HAWK = 0      # Target flock center
    FALCON = 1    # Chase nearest boid
    EAGLE = 2     # Hunt stragglers
    KITE = 3      # Patrol pattern
    OSPREY = 4    # Random target

def select_target(self, boids: List[Boid]) -> Optional[Tuple[float, float]]:
    if self.strategy == HuntingStrategy.HAWK:
        return self._target_flock_center(boids)
    elif self.strategy == HuntingStrategy.FALCON:
        return self._target_nearest_boid(boids)
    # ... etc

Tests: 23/23 passed

Status: Complete


Step 12.8: Boundary Regression Fix

Issue discovered: After multi-predator changes, boids were escaping screen boundaries.

Root cause: Boundary steering was being applied before all forces accumulated, then overwritten.

Solution: Refactored update loop to accumulate all forces first, then apply boundary steering, then update velocity.

Tests: 19/19 passed for boundary behavior

Status: Complete


Step 12.9: Hunting Improvements

Prompt: "Improve predator hunting behavior with attack cooldowns, successful catch detection, and better target switching."

Features added:

  • Attack cooldown (predator pauses briefly after close approach)
  • Target persistence (don't switch too frequently)
  • Edge-aware hunting for Eagle strategy
  • Smooth patrol interpolation for Kite strategy

Tests: 34/34 passed

Status: Complete


Step 12.10: Min Speed Slider

Prompt: "Add min_speed parameter to frontend controls so users can adjust minimum boid speed."

Changes:

  • Added slider to frontend
  • Added min_speed to SimulationConfig
  • Range: 0.0 to 3.0

Status: Complete


Step 15: Docker Containerization

Prompt: "Containerize the project with Docker for easy deployment. Use Docker Compose to orchestrate backend and frontend containers."

Architecture

┌─────────────────────────────────────────────────────────┐
│                    Docker Compose                        │
│                                                          │
│  ┌──────────────────┐      ┌──────────────────────────┐ │
│  │    Frontend      │      │       Backend            │ │
│  │    (Nginx)       │      │      (FastAPI)           │ │
│  │                  │      │                          │ │
│  │  Port 8080 ──────│─────►│  Port 8000               │ │
│  │  Static files    │  /ws │  WebSocket server        │ │
│  │  WS proxy        │      │  Simulation engine       │ │
│  └──────────────────┘      └──────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Files Created

File Purpose
docker-compose.yml Service orchestration
backend/Dockerfile Python 3.12-slim + uvicorn
backend/.dockerignore Exclude tests, cache
frontend/Dockerfile Multi-stage: Node build → Nginx
frontend/nginx.conf Static serving + WebSocket proxy
frontend/.dockerignore Exclude node_modules

WebSocket URL Configuration

Problem: Hardcoded ws://localhost:8000/ws doesn't work in Docker.

Solution: Dynamic URL detection:

const WS_URL = import.meta.env.VITE_WS_URL
  ? `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}${import.meta.env.VITE_WS_URL}`
  : 'ws://localhost:8000/ws';

Build Error Encountered

Error: TypeScript compilation failed:

src/App.tsx(120,10): error TS6133: 'frameData' is declared but its value is never read.

Solution: Removed unused variable from App.tsx.

Usage

# Build and run
docker compose up --build

# Access at http://localhost:8080

# Stop
docker compose down

Performance

Environment FPS
Local development ~60
Docker ~48

Status: Complete


Part II Summary

Implementation complete. Full interactive web application:

Component Status Tests
Configuration system 35
Pydantic models 36
Simulation manager 33
Presets system 22
WebSocket server 15
Static obstacles 34
Multiple predators 23
Predator strategies 23
Boundary fix 19
Hunting improvements 34
React frontend
Docker

Total Part II Tests: 357 passing


Reflection

What Types of Prompts Were Most Effective?

  1. Structured prompts with clear constraints worked best for implementation

    • Example: "Implement X with constraints A, B, C. Handle edge cases D, E, F."
  2. Test-first approach caught issues early

    • Writing test requirements in the prompt led to better coverage
  3. Incremental prompts (one step at a time) produced more reliable code

    • Large monolithic requests often missed edge cases
  4. Explicit edge case enumeration led to robust error handling

    • Example: "Handle: empty flock, zero distance, predator disabled"

What Mistakes Did GenAI Make?

Issue How Caught Resolution
Initial parameters too aggressive Visual inspection Multiple tuning iterations
Protected range exclusion missing from KDTree Equivalence tests Set subtraction fix
Sequential vs parallel update semantics Comparison testing Documented as intentional
Boundary steering order of operations User testing Refactored update loop
WebSocket race condition Integration testing Added handshake protocol
TypeScript unused variable Docker build Removed variable
Hardcoded WebSocket URL Docker testing Dynamic URL detection

Statistics

Metric Value
Total test files 15+
Part I tests 144
Part II tests 357
Total tests 501
Python files 20+
Lines of Python ~5,000
Lines of TypeScript ~800
Docker images 2
Figures generated 2
Parameter sweep runs 125
Parameter tuning iterations 4

Document Version: 12.0
Last Updated: January 2026
Total Tests: 501 passing