# Marshalling Yard Simulator: Comprehensive Development Analysis **Document Version:** 1.0 **Date:** 2025-10-12 **Prepared for:** Railway Simulation Project **Prepared by:** Claude Code Analysis --- ## Table of Contents 1. [Executive Summary](#executive-summary) 2. [Domain Analysis: Railway Marshalling Yards](#domain-analysis-railway-marshalling-yards) 3. [Core System Requirements](#core-system-requirements) 4. [Technology Stack Recommendations](#technology-stack-recommendations) 5. [Foundational Architecture Patterns](#foundational-architecture-patterns) 6. [Critical Tooling & Practices](#critical-tooling--practices) 7. [Physics & Mathematics Considerations](#physics--mathematics-considerations) 8. [Data Structures & Algorithms](#data-structures--algorithms) 9. [Development Roadmap](#development-roadmap) 10. [Risk Assessment & Mitigation](#risk-assessment--mitigation) 11. [Resources & References](#resources--references) --- ## Executive Summary A **marshalling yard simulator** is a complex domain-specific application that combines: - **Real-time simulation** (physics, pathfinding, collision detection) - **Graph theory** (track networks, routing algorithms) - **State management** (individual railcar tracking, consist formation) - **User interaction** (direct locomotive control, switching operations) - **Extensibility** (custom yard layouts, modular design) This document provides a comprehensive analysis of considerations for building such a system, with emphasis on foundational tooling and practices that will determine long-term project success. **Key Recommendation:** Invest heavily in **architecture design**, **version control workflow**, and **simulation core** before building visual features. Poor foundations will compound technical debt exponentially in simulation projects. --- ## Domain Analysis: Railway Marshalling Yards ### What is a Marshalling Yard? A **marshalling yard** (also called a classification yard or shunting yard) is a railway facility where freight cars are sorted and assembled into complete trains for their destinations. **Core Operations:** 1. **Reception** - Incoming trains arrive with mixed cargo destinations 2. **Classification** - Cars are sorted by destination using gravity or locomotives 3. **Marshalling** - Sorted cars are assembled into outbound trains 4. **Departure** - Completed trains leave for their destinations ### Real-World Complexity **Gravity Hump Yards:** - Cars roll down an artificial hill ("hump") - Switches (points) route cars to classification tracks - Retarders control speed automatically **Flat Switching Yards:** - Locomotives push/pull cars to sort them - More labor-intensive but suitable for terrain constraints **Elements to Model:** - Track topology (switches, crossings, sidings) - Rolling stock (locomotives, freight cars, tank cars, etc.) - Physics (momentum, braking, coupling/uncoupling) - Operations (switching logic, route planning, safety protocols) - Constraints (track capacity, clearance envelopes, gradients) ### Why This is Challenging to Simulate 1. **Graph Theory Complexity** - Track networks are directed multigraphs with constraints 2. **Physics Simulation** - Realistic train dynamics require numerical integration 3. **Combinatorial Optimization** - Routing and scheduling are NP-hard problems 4. **Real-Time Constraints** - User expects responsive controls and smooth animation 5. **State Explosion** - Hundreds of railcars with independent states --- ## Core System Requirements ### Functional Requirements #### 1. Track Network System - **Dynamic track graph** with nodes (junctions) and edges (track segments) - **Switch (turnout) states** - configurable routing at junctions - **Track occupancy detection** - which cars are on which segments - **Collision detection** - prevent derailments and conflicts - **Extensible yard designer** - users can create custom layouts #### 2. Rolling Stock Management - **Individual railcar entities** with properties: - Position, velocity, acceleration - Mass, length, coupler types - Cargo type, destination - **Locomotive control**: - Throttle, brake, reverser - Tractive effort curves - Multiple unit (MU) operation - **Consist formation** - coupled cars move as a unit #### 3. Switching Operations - **Manual switch control** - user throws switches - **Route setting** - automated path clearing - **Coupling/uncoupling** - dynamic consist modification - **Shunting moves** - push/pull operations #### 4. Simulation Engine - **Fixed timestep physics** for deterministic behavior - **Numerical integration** of train dynamics - **Event system** for state changes - **Save/load system** with full state serialization ### Non-Functional Requirements - **Performance:** 60 FPS with 100+ railcars active - **Scalability:** Support yards with 50+ tracks, 200+ switches - **Extensibility:** Plugin architecture for mods/custom content - **Usability:** Intuitive controls for complex operations - **Reliability:** No data loss, deterministic simulation --- ## Technology Stack Recommendations ### Decision Framework The choice of technology stack depends on: 1. **Target platform** (Desktop? Web? Mobile?) 2. **Graphics requirements** (2D top-down? 3D? Isometric?) 3. **Developer expertise** (What do you already know?) 4. **Community/ecosystem** (Libraries, tutorials, asset availability) ### Option 1: **Game Engine (Godot or Unity)** **Best for:** 3D/isometric graphics, desktop/mobile deployment, rich visual features #### Godot (Recommended for Open Source) - **Pros:** - Free and open source (MIT license) - Built-in 2D/3D engine with physics - GDScript (Python-like) or C# support - Excellent node-based scene system - Active community, good for indie projects - **Cons:** - Smaller ecosystem than Unity - Less mature 3D than Unity (improving rapidly) - Fewer tutorials for niche domains **Tech Stack:** - Engine: Godot 4.x - Language: GDScript (prototyping) → C# (performance-critical) - Version Control: Git with LFS (Large File Storage) - CI/CD: GitHub Actions for automated builds #### Unity (Industry Standard) - **Pros:** - Massive asset store and community - Mature 3D engine and tooling - Excellent performance profiling - Cross-platform deployment - **Cons:** - Proprietary (licensing complexities) - Heavier runtime footprint - Recent controversy over pricing model **Tech Stack:** - Engine: Unity 2022 LTS - Language: C# - Version Control: Git with Unity YAML mode - Package Manager: Unity Package Manager ### Option 2: **Custom Engine (C++ or Rust)** **Best for:** Maximum performance, learning low-level systems, full control #### C++ with SDL2/SFML - **Pros:** - Maximum performance control - Deep learning experience - No engine overhead - **Cons:** - Build everything from scratch - Longer development time - More complex tooling **Tech Stack:** - Language: C++17/20 - Graphics: SDL2 (2D) or Raylib (2D/3D) - Build: CMake + vcpkg (dependencies) - Testing: Google Test - Version Control: Git #### Rust with Bevy - **Pros:** - Memory safety without garbage collection - Modern ECS (Entity Component System) - Growing ecosystem - **Cons:** - Steep learning curve - Younger ecosystem - Compile times **Tech Stack:** - Language: Rust - Engine: Bevy 0.12+ - Build: Cargo - Testing: Built-in `cargo test` - Version Control: Git ### Option 3: **Web-Based (JavaScript/TypeScript)** **Best for:** Cross-platform accessibility, easy sharing, rapid prototyping #### TypeScript + Canvas/WebGL - **Pros:** - Runs anywhere (browser) - Easy to share (just a URL) - Rich ecosystem (npm) - Fast iteration - **Cons:** - Performance limits for complex simulations - Browser compatibility quirks **Tech Stack:** - Language: TypeScript - Graphics: PixiJS (2D) or Three.js (3D) - Build: Vite or Webpack - Testing: Jest + Playwright - Version Control: Git - Deployment: GitHub Pages or Netlify ### Option 4: **Python (Rapid Prototyping)** **Best for:** Algorithm validation, quick prototypes, educational purposes #### Python + Pygame - **Pros:** - Extremely fast prototyping - Easy to learn - Great for algorithm testing - **Cons:** - Performance ceiling - Limited for production-quality games **Tech Stack:** - Language: Python 3.11+ - Graphics: Pygame or Arcade - Testing: pytest - Version Control: Git - Environment: Poetry or pipenv --- ## Recommended Stack (Balanced Approach) **For a solo developer building their first simulation:** ### **Godot 4.x + GDScript → C#** **Rationale:** 1. **Low barrier to entry** - GDScript is easy, editor is intuitive 2. **Built-in physics** - Don't reinvent the wheel 3. **2D mode** - Perfect for top-down yard view 4. **Free & open source** - Aligns with sharing/educational goals 5. **Migration path** - Start with GDScript, optimize with C# later **Initial Setup:** ```bash # Install Godot 4.2+ # Create new project # Enable C# support if needed (.NET 6.0+) # Initialize Git repository git init git lfs install git lfs track "*.png" git lfs track "*.jpg" git lfs track "*.wav" git lfs track "*.ogg" ``` --- ## Foundational Architecture Patterns ### 1. Entity Component System (ECS) **Why:** Separates data (components) from behavior (systems), enabling massive scalability. **Example Structure:** ``` Entities: - Locomotive (Position, Velocity, LocomotiveController, Renderable) - FreightCar (Position, Velocity, CargoData, Renderable) - Track (TrackSegment, Occupancy, Renderable) - Switch (SwitchState, Position, Renderable) Components: - Position (x, y, rotation) - Velocity (vx, vy, angular_velocity) - Mass (kg) - LocomotiveController (throttle, brake, reverser) - TrackSegment (start_node, end_node, length, curve_data) Systems: - PhysicsSystem (updates Position based on Velocity) - LocomotiveSystem (applies forces based on LocomotiveController) - CollisionSystem (detects overlaps) - RenderSystem (draws entities) ``` **Benefits:** - Easy to add new entity types - Systems can be parallelized - Data-oriented design = cache-friendly ### 2. Graph-Based Track Network **Model tracks as a directed graph:** ```python class TrackNode: id: int position: (x, y) connected_edges: List[TrackEdge] class TrackEdge: id: int start_node: TrackNode end_node: TrackNode length: float curve_parameters: BezierCurve # ← Your friend's Bezier reference! current_occupants: List[RailcarEntity] class Switch: node: TrackNode possible_routes: List[(incoming_edge, outgoing_edge)] current_state: int # Index into possible_routes ``` **Algorithms Needed:** - **Pathfinding:** A* or Dijkstra for routing locomotives - **Occupancy tracking:** Which cars are on which edges - **Deadlock detection:** Prevent routing conflicts - **Route reservation:** Lock paths before movement ### 3. Physics Simulation Architecture **Use a fixed timestep loop:** ```python class SimulationEngine: def __init__(self): self.accumulator = 0.0 self.dt = 1.0 / 60.0 # 60 Hz physics def update(self, frame_time): self.accumulator += frame_time # Fixed timestep updates while self.accumulator >= self.dt: self.physics_step(self.dt) self.accumulator -= self.dt # Render with interpolation alpha = self.accumulator / self.dt self.render(alpha) ``` **Why fixed timestep?** - Deterministic simulation (same inputs = same outputs) - Stable numerical integration - Reproducible bugs (critical for testing) ### 4. State Machine for Operations **Model complex operations (switching, coupling) as state machines:** ``` Coupling Operation: 1. IDLE → user initiates coupling 2. APPROACHING → locomotive moves toward car 3. CONTACT_MADE → collision detected 4. COUPLING → animation plays 5. COUPLED → consist updated 6. IDLE Switching Operation: 1. IDLE 2. ROUTE_REQUESTED → user clicks destination 3. PATHFINDING → A* calculates route 4. SWITCH_ALIGNMENT → throw necessary switches 5. ROUTE_LOCKED → reserve track segments 6. MOVEMENT → locomotive follows path 7. ROUTE_RELEASED → free track segments 8. IDLE ``` **Tools:** State pattern, or use a FSM library (e.g., `stateless` in C#) ### 5. Command Pattern for Undo/Redo **Every user action should be a command:** ```python class Command: def execute(self): pass def undo(self): pass class ThrowSwitchCommand(Command): def __init__(self, switch, new_state): self.switch = switch self.old_state = switch.current_state self.new_state = new_state def execute(self): self.switch.current_state = self.new_state def undo(self): self.switch.current_state = self.old_state class CommandHistory: def __init__(self): self.history = [] self.current = -1 def execute(self, command): # Clear redo stack self.history = self.history[:self.current + 1] command.execute() self.history.append(command) self.current += 1 ``` **Benefits:** - Undo/redo support (critical for a simulator) - Macro recording (automate repetitive tasks) - Network play (send commands over network) ### 6. Save/Load System Architecture **Serialize the entire simulation state:** ```json { "version": "1.0.0", "timestamp": "2025-10-12T14:30:00Z", "yard_layout": "marshalling_yard_01", "entities": [ { "type": "Locomotive", "id": "loco_001", "position": {"x": 150.5, "y": 200.0, "rotation": 90}, "velocity": {"vx": 0.0, "vy": 2.5}, "controller": {"throttle": 0.4, "brake": 0.0} }, { "type": "FreightCar", "id": "car_042", "position": {"x": 150.5, "y": 185.0, "rotation": 90}, "cargo": {"type": "coal", "weight": 50000}, "coupled_to": "loco_001" } ], "switches": [ {"id": "sw_01", "state": 0}, {"id": "sw_02", "state": 1} ], "simulation_time": 1458.5 } ``` **Critical:** Version your save format! Add migration logic for backward compatibility. --- ## Critical Tooling & Practices ### 1. Version Control: Git Workflow **This is NON-NEGOTIABLE for iterative development.** #### Repository Structure ``` marshalling-yard-sim/ ├── .git/ ├── .gitignore ├── .gitattributes # Git LFS configuration ├── README.md ├── docs/ │ ├── architecture.md │ ├── physics_model.md │ └── track_format.md ├── src/ # Source code │ ├── core/ │ ├── entities/ │ ├── systems/ │ └── ui/ ├── assets/ # Art, sounds (use Git LFS!) │ ├── sprites/ │ ├── sounds/ │ └── tracks/ # Yard layout files ├── tests/ # Unit + integration tests └── tools/ # Custom editors, validators ``` #### Branching Strategy: **GitHub Flow** (Simplified) ``` main (always deployable) ↑ └─ feature/track-editor └─ feature/physics-v2 └─ bugfix/collision-detection └─ experiment/bezier-curves ``` **Workflow:** 1. `main` branch is always stable (compiles, tests pass) 2. Create feature branch: `git checkout -b feature/locomotive-control` 3. Commit iteratively with clear messages 4. Merge back to `main` when complete 5. Tag releases: `git tag v0.1.0` **Branch Naming Convention:** - `feature/` - New functionality - `bugfix/` - Fix existing issues - `experiment/` - Proof-of-concept (may be discarded) - `refactor/` - Code cleanup without behavior change #### Commit Message Format Use **Conventional Commits:** ``` (): [optional body] [optional footer] ``` **Examples:** ``` feat(physics): implement realistic braking curves Added exponential decay model for brake application based on real-world freight train data. Closes #42 ``` ``` fix(pathfinding): correct A* heuristic for curved tracks Previous heuristic used Euclidean distance, which underestimated cost for curved paths. Now uses arc length estimation. ``` **Types:** `feat`, `fix`, `docs`, `refactor`, `test`, `chore` #### Git LFS for Assets **Large binary files (images, sounds) should use Git LFS:** ```bash git lfs install git lfs track "*.png" git lfs track "*.psd" git lfs track "*.wav" git lfs track "*.blend" ``` Why? Git doesn't handle binary diffs well. LFS stores pointers in repo, actual files in LFS server. ### 2. Continuous Integration (CI) **Automate testing and builds on every commit.** #### Example: GitHub Actions Workflow ```yaml # .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: lfs: true - name: Setup Godot uses: chickensoft-games/setup-godot@v1 with: version: 4.2.0 - name: Build project run: godot --headless --export "Linux/X11" build/game.x86_64 - name: Run tests run: godot --headless -s addons/gut/gut_cmdln.gd - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: linux-build path: build/ ``` **Benefits:** - Catch broken builds immediately - Enforce test coverage - Generate nightly builds - Prevent regressions ### 3. Unit Testing Framework **Test your simulation logic independently of rendering.** #### Example: Godot GUT (Godot Unit Test) ```gdscript # test_track_graph.gd extends GutTest func test_pathfinding_simple_route(): # Arrange var graph = TrackGraph.new() var node_a = graph.add_node(Vector2(0, 0)) var node_b = graph.add_node(Vector2(100, 0)) graph.add_edge(node_a, node_b, 100.0) # Act var path = graph.find_path(node_a, node_b) # Assert assert_eq(path.size(), 2) assert_eq(path[0], node_a) assert_eq(path[1], node_b) func test_pathfinding_with_switch(): # Arrange var graph = TrackGraph.new() var node_a = graph.add_node(Vector2(0, 0)) var switch_node = graph.add_node(Vector2(50, 0)) var node_b = graph.add_node(Vector2(100, 0)) var node_c = graph.add_node(Vector2(100, 50)) graph.add_edge(node_a, switch_node, 50.0) graph.add_edge(switch_node, node_b, 50.0) graph.add_edge(switch_node, node_c, 50.0) var switch = Switch.new(switch_node, [ [node_a, node_b], # State 0: straight [node_a, node_c] # State 1: diverging ]) # Act switch.set_state(1) var path = graph.find_path(node_a, node_c) # Assert assert_true(path != null) assert_eq(path[-1], node_c) ``` **Test Coverage Goals:** - Core algorithms: 90%+ (pathfinding, physics) - Data structures: 80%+ (graph, entity management) - UI code: 50%+ (harder to test, less critical) ### 4. Documentation: Living Architecture Docs **Maintain up-to-date architecture documentation.** #### Arc42 Template (Recommended) ``` docs/ ├── 01_introduction.md # Goals, stakeholders ├── 02_constraints.md # Technical, organizational limits ├── 03_context.md # System boundaries ├── 04_solution_strategy.md # Key decisions ├── 05_building_blocks.md # Component diagram ├── 06_runtime.md # Sequence diagrams ├── 07_deployment.md # How it's deployed ├── 08_crosscutting.md # Logging, error handling ├── 09_decisions.md # Architecture Decision Records └── 10_quality.md # Performance, scalability ``` #### Architecture Decision Records (ADRs) **Document significant decisions with context:** ```markdown # ADR-001: Use Godot Engine ## Status Accepted ## Context Need to choose a technology stack for the marshalling yard simulator. Requirements: 2D graphics, physics, cross-platform, low cost. ## Decision Use Godot 4.2 with GDScript. ## Consequences **Positive:** - Free and open source - Built-in 2D engine and physics - Fast iteration with hot-reload **Negative:** - Smaller ecosystem than Unity - GDScript may have performance limits (can migrate to C#) **Neutral:** - Need to learn GDScript ``` Store ADRs in `docs/adr/`. ### 5. Profiling & Performance Tools **Identify bottlenecks early.** #### Godot Profiler - Built-in profiler: `Debug → Profiler` - Shows frame time breakdown - Memory usage monitoring #### Custom Metrics ```gdscript # performance_monitor.gd extends Node var frame_times = [] var physics_times = [] func _process(delta): frame_times.append(delta) if frame_times.size() > 300: # 5 seconds at 60 FPS frame_times.pop_front() var avg = frame_times.reduce(func(a, b): return a + b) / frame_times.size() if avg > 1.0 / 60.0: push_warning("Frame rate below 60 FPS: %.2f ms" % (avg * 1000)) ``` **Performance Budgets:** - Physics step: < 5ms - Pathfinding: < 10ms - Rendering: < 10ms - **Total frame time: < 16.67ms (60 FPS)** ### 6. Asset Pipeline **Standardize asset creation and import.** #### Naming Conventions ``` sprites/ rolling_stock/ locomotive_emd_gp38_side.png locomotive_emd_gp38_top.png freight_car_boxcar_50ft_side.png freight_car_boxcar_50ft_top.png track/ track_straight_100m.png track_curve_r500m_30deg.png switch_left_turnout.png sounds/ locomotive/ diesel_engine_idle_loop.ogg diesel_engine_rev_up.ogg air_brake_release.ogg ``` #### Import Settings (Godot) - **Pixel art:** Import as 2D, filter: nearest - **HD sprites:** Import as 2D, filter: linear, mipmaps: on - **Sounds:** Compress to OGG Vorbis, loop: enabled for ambient ### 7. Issue Tracking **Use GitHub Issues or dedicated tool (Jira, Linear).** #### Issue Template ```markdown **Type:** Bug / Feature / Enhancement **Description:** [Clear description of the issue] **Steps to Reproduce:** (for bugs) 1. Start new yard 2. Place locomotive on track 3 3. Set switch 5 to diverging 4. Observe crash **Expected Behavior:** [What should happen] **Actual Behavior:** [What actually happens] **Screenshots/Logs:** [Attach relevant info] **Priority:** Low / Medium / High / Critical **Labels:** bug, physics, pathfinding, ui ``` #### Kanban Board ``` Backlog → To Do → In Progress → Review → Done ``` Use GitHub Projects or external tool. --- ## Physics & Mathematics Considerations ### 1. Train Dynamics Model **Simplified 1D model for each consist:** #### Forces on a Train ``` F_net = F_tractive - F_resistance - F_gravity Where: - F_tractive = locomotive pulling force (throttle-dependent) - F_resistance = air drag + rolling resistance - F_gravity = component of weight on grade F_resistance = C_r * v + C_d * v^2 C_r = rolling resistance coefficient (≈ 0.001 for steel on steel) C_d = air drag coefficient (depends on train frontal area) v = velocity F_gravity = m * g * sin(θ) m = total consist mass g = 9.81 m/s² θ = track gradient angle ``` #### Integration (Euler method for simplicity) ```python def physics_step(dt): for consist in consists: # Calculate forces F_tractive = consist.locomotive.tractive_effort(consist.throttle) F_resistance = rolling_resistance(consist) + air_drag(consist) F_gravity = gravity_component(consist) F_net = F_tractive - F_resistance - F_gravity # Newton's second law: F = ma → a = F/m acceleration = F_net / consist.total_mass # Euler integration consist.velocity += acceleration * dt consist.position += consist.velocity * dt ``` **Note:** For better accuracy, use **Verlet integration** or **RK4**, but Euler is fine to start. ### 2. Braking System **Air brakes have delay and non-linear response:** ```python class AirBrakeSystem: def __init__(self): self.brake_pipe_pressure = 90 # PSI (fully charged) self.application_rate = 10 # PSI/second self.release_rate = 5 # PSI/second def update(self, dt, brake_command): if brake_command > 0: # Apply brakes target_pressure = 90 - (brake_command * 70) # Max reduction: 70 PSI self.brake_pipe_pressure -= self.application_rate * dt self.brake_pipe_pressure = max(self.brake_pipe_pressure, target_pressure) else: # Release brakes self.brake_pipe_pressure += self.release_rate * dt self.brake_pipe_pressure = min(self.brake_pipe_pressure, 90) def get_braking_force(self, consist): # Lower pressure = more braking pressure_reduction = 90 - self.brake_pipe_pressure braking_ratio = pressure_reduction / 70 # 0.0 to 1.0 max_braking_force = consist.total_mass * 9.81 * 0.3 # 30% of weight return braking_ratio * max_braking_force ``` **Result:** Brakes don't apply instantly! This is critical for gameplay feel. ### 3. Coupling Mechanics **Slack action:** Real trains have loose couplers with 6-12 inches of slack per car. ```python class Coupler: def __init__(self): self.slack = 0.0 # Current slack distance (meters) self.max_slack = 0.15 # 15 cm per coupler self.stiffness = 50000 # Spring constant (N/m) self.damping = 5000 # Damping coefficient (Ns/m) def calculate_force(self, relative_velocity): # Spring-damper model if abs(self.slack) > self.max_slack: # Slack taken up, coupler engaged spring_force = self.stiffness * (abs(self.slack) - self.max_slack) damping_force = self.damping * relative_velocity return spring_force + damping_force else: # Slack not taken up, no force return 0.0 ``` **Gameplay impact:** Consists behave like connected spring-dampers, creating realistic "jerking" when accelerating/braking. ### 4. Bezier Curves for Track Layout **Your friend mentioned Bezier curves - here's why they're perfect for tracks:** #### Cubic Bezier Curve ```python class CubicBezier: def __init__(self, p0, p1, p2, p3): self.p0 = p0 # Start point self.p1 = p1 # Start control point self.p2 = p2 # End control point self.p3 = p3 # End point def point_at(self, t): # t ∈ [0, 1] u = 1 - t return (u**3 * self.p0 + 3 * u**2 * t * self.p1 + 3 * u * t**2 * self.p2 + t**3 * self.p3) def tangent_at(self, t): # First derivative (direction) u = 1 - t return (3 * u**2 * (self.p1 - self.p0) + 6 * u * t * (self.p2 - self.p1) + 3 * t**2 * (self.p3 - self.p2)) def length(self, steps=100): # Approximate arc length by sampling length = 0.0 prev = self.point_at(0) for i in range(1, steps + 1): curr = self.point_at(i / steps) length += (curr - prev).length() prev = curr return length ``` **Why Bezier for tracks?** - **Smooth curves:** No sharp corners (unlike polygonal paths) - **Easy editing:** Move control points to reshape curve - **Tangent calculation:** Needed for train rotation along path - **Arc length:** Can parameterize by distance (needed for positioning trains) **Track Editor Workflow:** 1. User clicks to place start point (p0) 2. Drag to set direction (p1 = p0 + drag_vector) 3. Click to place end point (p3) 4. Drag to set end direction (p2 = p3 - drag_vector) 5. System generates 100 sample points for collision detection ### 5. Collision Detection **Trains are long, thin objects - use swept AABBs or capsule colliders.** #### Swept AABB (Axis-Aligned Bounding Box) ```python class Train: def get_aabb(self): # Bounding box for entire consist min_x = min(car.position.x for car in self.cars) max_x = max(car.position.x + car.length for car in self.cars) min_y = min(car.position.y - car.width/2 for car in self.cars) max_y = max(car.position.y + car.width/2 for car in self.cars) return AABB(min_x, min_y, max_x, max_y) def check_collision(train_a, train_b): aabb_a = train_a.get_aabb() aabb_b = train_b.get_aabb() # AABB intersection test if (aabb_a.max_x < aabb_b.min_x or aabb_a.min_x > aabb_b.max_x): return False if (aabb_a.max_y < aabb_b.min_y or aabb_a.min_y > aabb_b.max_y): return False # Potential collision, do per-car check for car_a in train_a.cars: for car_b in train_b.cars: if car_a.collides_with(car_b): return True return False ``` **Optimization:** Use spatial partitioning (quadtree) for large yards with many trains. --- ## Data Structures & Algorithms ### 1. Track Graph (Adjacency List) ```python class TrackGraph: def __init__(self): self.nodes = {} # node_id → TrackNode self.edges = {} # edge_id → TrackEdge def add_node(self, node_id, position): self.nodes[node_id] = TrackNode(node_id, position) def add_edge(self, edge_id, start_id, end_id, curve): edge = TrackEdge(edge_id, start_id, end_id, curve) self.edges[edge_id] = edge self.nodes[start_id].outgoing_edges.append(edge) self.nodes[end_id].incoming_edges.append(edge) ``` **Time Complexity:** - Add node/edge: O(1) - Find neighbors: O(degree of node) - Pathfinding (A*): O(E log V) where E = edges, V = nodes ### 2. A* Pathfinding for Route Planning ```python import heapq def find_path(graph, start_node_id, end_node_id, switch_states): """ A* pathfinding considering current switch states. Returns: List of edge IDs forming the path, or None if no path exists. """ open_set = [] heapq.heappush(open_set, (0, start_node_id)) came_from = {} g_score = {start_node_id: 0} f_score = {start_node_id: heuristic(start_node_id, end_node_id)} while open_set: _, current = heapq.heappop(open_set) if current == end_node_id: return reconstruct_path(came_from, current) for edge in graph.nodes[current].outgoing_edges: # Check if switch allows this edge if not is_edge_available(edge, switch_states): continue neighbor = edge.end_node_id tentative_g = g_score[current] + edge.length if tentative_g < g_score.get(neighbor, float('inf')): came_from[neighbor] = (current, edge.id) g_score[neighbor] = tentative_g f_score[neighbor] = tentative_g + heuristic(neighbor, end_node_id) heapq.heappush(open_set, (f_score[neighbor], neighbor)) return None # No path found def heuristic(node_id_a, node_id_b): # Euclidean distance (optimistic estimate) pos_a = graph.nodes[node_id_a].position pos_b = graph.nodes[node_id_b].position return ((pos_a.x - pos_b.x)**2 + (pos_a.y - pos_b.y)**2)**0.5 ``` **Enhancement:** Use **bidirectional A*** for faster searches in large yards. ### 3. Spatial Partitioning (Quadtree) **For efficient collision detection when there are 100+ trains:** ```python class Quadtree: def __init__(self, boundary, capacity=4): self.boundary = boundary # AABB self.capacity = capacity self.entities = [] self.divided = False self.northwest = None self.northeast = None self.southwest = None self.southeast = None def insert(self, entity): if not self.boundary.contains(entity.position): return False if len(self.entities) < self.capacity: self.entities.append(entity) return True if not self.divided: self.subdivide() return (self.northwest.insert(entity) or self.northeast.insert(entity) or self.southwest.insert(entity) or self.southeast.insert(entity)) def query_range(self, range_aabb): found = [] if not self.boundary.intersects(range_aabb): return found for entity in self.entities: if range_aabb.contains(entity.position): found.append(entity) if self.divided: found.extend(self.northwest.query_range(range_aabb)) found.extend(self.northeast.query_range(range_aabb)) found.extend(self.southwest.query_range(range_aabb)) found.extend(self.southeast.query_range(range_aabb)) return found ``` **Usage:** ```python # Each frame: quadtree = Quadtree(yard_boundary) for train in all_trains: quadtree.insert(train) # Collision check for specific train: nearby = quadtree.query_range(train.get_expanded_aabb()) for other in nearby: if check_collision(train, other): handle_collision(train, other) ``` **Performance:** Reduces collision checks from O(n²) to O(n log n). ### 4. Event System (Observer Pattern) **Decouple systems using events:** ```python class EventBus: def __init__(self): self.listeners = {} # event_type → [callback_functions] def subscribe(self, event_type, callback): if event_type not in self.listeners: self.listeners[event_type] = [] self.listeners[event_type].append(callback) def publish(self, event): if event.type in self.listeners: for callback in self.listeners[event.type]: callback(event) # Usage: event_bus = EventBus() # UI subscribes to coupling events def on_coupling_complete(event): show_notification(f"Coupled {event.car_a} to {event.car_b}") event_bus.subscribe("coupling_complete", on_coupling_complete) # Physics system publishes events event_bus.publish(CouplingCompleteEvent(car_a, car_b)) ``` **Events to implement:** - `TrainCoupled` - `TrainUncoupled` - `SwitchThrown` - `CollisionDetected` - `RouteCompleted` - `YardLayoutChanged` --- ## Development Roadmap ### Phase 0: Foundation (Weeks 1-2) **Goal:** Set up project infrastructure and core architecture. **Milestones:** 1. ✅ Choose technology stack (Godot recommended) 2. ✅ Initialize Git repository with proper `.gitignore` and LFS 3. ✅ Set up CI pipeline (GitHub Actions) 4. ✅ Create project structure (folders, naming conventions) 5. ✅ Write architecture documentation (Arc42 template) 6. ✅ Implement basic ECS or scene structure 7. ✅ Set up unit testing framework **Deliverable:** Empty project that compiles, runs, and has passing (trivial) tests. ### Phase 1: Core Simulation (Weeks 3-6) **Goal:** Implement physics and track graph without graphics. **Milestones:** 1. ✅ Implement `TrackGraph` class with nodes and edges 2. ✅ Implement Bezier curve evaluation for curved tracks 3. ✅ Implement `Train` entity with position, velocity 4. ✅ Implement 1D physics model (acceleration, braking) 5. ✅ Implement train-track positioning (which track, distance along) 6. ✅ Write unit tests for pathfinding (A*) 7. ✅ Write unit tests for physics integration **Deliverable:** Headless simulation that logs train movements. **Test:** ``` Create simple yard: A --[100m]--> B --[100m]--> C Place train at A Command train to move to C at 10 m/s Verify: Train reaches C in ~20 seconds ``` ### Phase 2: Basic Visualization (Weeks 7-8) **Goal:** Render tracks and trains in 2D top-down view. **Milestones:** 1. ✅ Create camera system (pan, zoom) 2. ✅ Render track segments as lines or sprites 3. ✅ Render trains as rectangles (placeholder) 4. ✅ Implement train rotation along track direction 5. ✅ Add debug overlay (velocity, acceleration vectors) **Deliverable:** You can SEE the trains moving along tracks. ### Phase 3: Switching & Control (Weeks 9-11) **Goal:** User can control switches and locomotives. **Milestones:** 1. ✅ Implement `Switch` entity with state management 2. ✅ Add UI for throwing switches (click to toggle) 3. ✅ Implement route planning with A* considering switch states 4. ✅ Add locomotive control panel (throttle, brake, direction) 5. ✅ Implement automatic route following (train follows path to destination) 6. ✅ Add route reservation system (prevent conflicts) **Deliverable:** User can route a train from track 1 to track 5 by clicking switches and setting throttle. ### Phase 4: Coupling & Consists (Weeks 12-14) **Goal:** Trains can couple/uncouple to form consists. **Milestones:** 1. ✅ Implement `Consist` class (group of coupled cars) 2. ✅ Implement coupling detection (trains touching) 3. ✅ Implement coupling operation (merge into consist) 4. ✅ Implement uncoupling operation (split consist) 5. ✅ Add UI for coupling/uncoupling 6. ✅ Implement slack action physics (spring-damper couplers) **Deliverable:** User can assemble a train from individual cars. ### Phase 5: Yard Designer (Weeks 15-17) **Goal:** User can create custom yard layouts. **Milestones:** 1. ✅ Implement track placement tool (drag to draw track) 2. ✅ Implement Bezier curve editing (control points) 3. ✅ Implement switch placement tool 4. ✅ Add track deletion 5. ✅ Implement save/load yard layout (JSON format) 6. ✅ Add validation (no overlapping tracks, dead ends, etc.) **Deliverable:** User can create a custom marshalling yard and save it. ### Phase 6: Polish & Features (Weeks 18-20) **Goal:** Add quality-of-life features and polish. **Milestones:** 1. ✅ Improve graphics (better sprites for trains, tracks) 2. ✅ Add sound effects (engine, brakes, coupling) 3. ✅ Implement undo/redo system 4. ✅ Add performance optimizations (spatial partitioning) 5. ✅ Write user documentation 6. ✅ Add tutorials/scenarios **Deliverable:** Polished game ready for alpha testing. ### Phase 7: Advanced Features (Future) **Optional features for post-release:** - Multiplayer (network synchronization) - Train AI (pathfinding agents) - Economic simulation (cargo routing, revenue) - 3D graphics mode - Mod support (custom cars, tracks) --- ## Risk Assessment & Mitigation ### Technical Risks | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | **Physics instability** | High | Medium | Use fixed timestep, test with extreme cases, add numerical damping | | **Pathfinding performance** | Medium | Medium | Implement spatial partitioning, cache paths, use bidirectional A* | | **Bezier curve complexity** | Medium | Low | Start with straight tracks, add curves later. Use existing libraries (e.g., Godot's Curve2D) | | **Collision detection bugs** | High | High | Write comprehensive unit tests, visualize collision boxes, use proven algorithms | | **Save file corruption** | High | Low | Version save format, validate on load, keep backups | | **Scope creep** | High | High | **Define MVP strictly**, use issue tracker, prioritize ruthlessly | ### Project Management Risks | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | **Loss of motivation** | Critical | Medium | Work in small iterations, celebrate milestones, share progress publicly | | **Feature creep** | High | High | **Stick to roadmap**, defer "nice-to-haves" to Phase 7 | | **Underestimating complexity** | High | High | Build prototypes early, ask for help, read similar projects' postmortems | | **No version control discipline** | Medium | Medium | **Commit daily**, use branch naming conventions, code review (even solo) | --- ## Resources & References ### Similar Projects (Study These!) 1. **OpenTTD** (Open Transport Tycoon Deluxe) - Open source transport simulation - Excellent pathfinding and route management - GitHub: https://github.com/OpenTTD/OpenTTD 2. **Simutrans** - Transport simulation game - Good graph-based track system - Website: https://www.simutrans.com 3. **Rolling Line** (VR train sim) - Realistic physics, detailed yards - Study their coupling mechanics 4. **Derail Valley** (VR train sim) - Excellent locomotive controls - Realistic braking behavior ### Academic Papers 1. **"Train Scheduling and Routing Using A* Search"** - Research on railway optimization 2. **"Real-Time Physics Simulation in Games"** - Fixed timestep integration techniques 3. **"Graph Algorithms for Railway Networks"** - Pathfinding in constrained graphs ### Books 1. **"Game Programming Patterns"** by Robert Nystrom - Essential design patterns (Command, Observer, State) - Free online: https://gameprogrammingpatterns.com 2. **"Real-Time Collision Detection"** by Christer Ericson - Comprehensive collision algorithms - ISBN: 978-1558607323 3. **"Game Physics Engine Development"** by Ian Millington - Physics integration, constraints, numerical methods - ISBN: 978-0123819765 4. **"The Art of Readable Code"** by Boswell & Foucher - Code quality and maintainability - ISBN: 978-0596802295 ### Online Resources 1. **Godot Documentation** - https://docs.godotengine.org/en/stable/ 2. **GDQuest** (Godot tutorials) - https://www.gdquest.com - Excellent YouTube channel for Godot patterns 3. **Red Blob Games** (Interactive algorithm visualizations) - https://www.redblobgames.com - A* pathfinding, Bezier curves, more 4. **Gaffer on Games** (Physics integration) - https://gafferongames.com/post/integration_basics/ - **Essential reading for fixed timestep loops** 5. **Railway Technical Web Pages** - http://www.railway-technical.com - Real-world railway engineering reference ### Communities 1. **r/gamedev** (Reddit) - General game development discussions - Weekly feedback threads 2. **r/godot** (Reddit) - Godot-specific help and resources 3. **Godot Discord** - Real-time help from community 4. **GameDev.net** - Forums for algorithm discussions --- ## Appendix A: Glossary of Railway Terms | Term | Definition | |------|------------| | **Marshalling Yard** | Facility where freight cars are sorted by destination | | **Classification** | Process of sorting cars into groups | | **Hump Yard** | Yard using gravity to sort cars over an artificial hill | | **Retarder** | Braking device to control car speed in hump yards | | **Turnout / Switch** | Track junction allowing route selection | | **Consist** | Group of coupled railcars (includes locomotives) | | **Coupler** | Mechanical device connecting cars | | **Slack Action** | Play/looseness in couplers causing jerking motion | | **Drawbar** | Force transmitted through couplers | | **Tractive Effort** | Pulling force generated by locomotive | | **Dynamic Braking** | Using locomotive motors as generators to brake | | **Air Brake** | Braking system using compressed air | | **Siding** | Auxiliary track parallel to main line | | **Clearance** | Minimum safe distance between trains/objects | | **Gradient** | Track slope (percent or per-mille) | --- ## Appendix B: Initial Git Setup Checklist ```bash # 1. Create repository mkdir marshalling-yard-sim cd marshalling-yard-sim git init # 2. Configure user (if not already set globally) git config user.name "Your Name" git config user.email "your.email@example.com" # 3. Create .gitignore cat > .gitignore << EOF # Godot-specific .import/ export.cfg export_presets.cfg .mono/ data_*/ # OS .DS_Store Thumbs.db # IDE .vscode/ .idea/ # Builds builds/ *.exe *.pck *.dmg *.apk EOF # 4. Set up Git LFS git lfs install cat > .gitattributes << EOF *.png filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text *.psd filter=lfs diff=lfs merge=lfs -text *.wav filter=lfs diff=lfs merge=lfs -text *.ogg filter=lfs diff=lfs merge=lfs -text *.mp3 filter=lfs diff=lfs merge=lfs -text *.blend filter=lfs diff=lfs merge=lfs -text EOF # 5. Create initial project structure mkdir -p src/{core,entities,systems,ui} mkdir -p assets/{sprites,sounds,tracks} mkdir -p tests mkdir -p docs/adr mkdir -p tools # 6. Create README cat > README.md << EOF # Marshalling Yard Simulator A realistic railway marshalling yard simulation game. ## Features - Individual car management - Direct locomotive control - Extensible yard designer - Realistic physics ## Tech Stack - Engine: Godot 4.2 - Language: GDScript / C# ## Getting Started [Installation instructions] ## License [Your chosen license] EOF # 7. Initial commit git add . git commit -m "chore: initial project setup - Initialize Git repository with LFS - Create project structure - Add README and .gitignore" # 8. Create main branch (if using GitHub) git branch -M main # 9. Add remote (replace with your URL) # git remote add origin https://github.com/yourusername/marshalling-yard-sim.git # git push -u origin main ``` --- ## Appendix C: Sample First Feature Branch Workflow ```bash # Start from main branch git checkout main git pull # Ensure up-to-date # Create feature branch for track graph git checkout -b feature/track-graph-foundation # Work on the feature... # Create src/core/track_graph.gd # Create tests/test_track_graph.gd # Commit iteratively git add src/core/track_graph.gd git commit -m "feat(core): implement TrackGraph node/edge structure" git add tests/test_track_graph.gd git commit -m "test(core): add unit tests for TrackGraph" # Run tests to ensure they pass # (Godot GUT framework) # Push feature branch to remote git push -u origin feature/track-graph-foundation # Create pull request on GitHub (or merge locally if solo) # After review/testing: git checkout main git merge feature/track-graph-foundation git push origin main # Tag milestone git tag v0.1.0-alpha -m "Milestone: Track graph foundation complete" git push origin v0.1.0-alpha # Delete feature branch (optional, keeps repo clean) git branch -d feature/track-graph-foundation git push origin --delete feature/track-graph-foundation ``` --- ## Appendix D: Performance Benchmarking Template ```gdscript # performance_test.gd extends GutTest func test_pathfinding_performance(): # Arrange: Create large yard (50 nodes, 100 edges) var graph = create_large_yard() var start_node = graph.nodes[0] var end_node = graph.nodes[49] # Act: Measure pathfinding time var start_time = Time.get_ticks_usec() var iterations = 1000 for i in range(iterations): var path = graph.find_path(start_node, end_node) assert_not_null(path) var end_time = Time.get_ticks_usec() var avg_time_ms = (end_time - start_time) / 1000.0 / iterations # Assert: Should complete in < 10ms print("Average pathfinding time: %.3f ms" % avg_time_ms) assert_lt(avg_time_ms, 10.0, "Pathfinding too slow!") func test_physics_step_performance(): # Arrange: 100 trains on tracks var trains = create_test_trains(100) # Act: Measure physics update time var start_time = Time.get_ticks_usec() var iterations = 600 # 10 seconds at 60 FPS for i in range(iterations): PhysicsSystem.update(trains, 1.0/60.0) var end_time = Time.get_ticks_usec() var avg_time_ms = (end_time - start_time) / 1000.0 / iterations # Assert: Should stay under 5ms budget print("Average physics step: %.3f ms" % avg_time_ms) assert_lt(avg_time_ms, 5.0, "Physics step too slow!") ``` --- ## Final Recommendations ### Critical Success Factors (In Order of Importance) 1. **Git Workflow Discipline** - Commit daily, even if code isn't "done" - Use branches for all features (no direct commits to main) - Write meaningful commit messages - **This is the #1 tool for iteration and checkpoints** 2. **Start Simple, Iterate** - Don't build the Bezier curve editor first - Start with straight tracks, add curves later - Placeholder graphics are fine - **Gameplay/simulation first, polish later** 3. **Architecture Before Code** - Spend 20% of time on design docs - Draw diagrams before writing classes - Discuss architecture decisions (even with yourself via ADRs) - **Refactoring is expensive; planning is cheap** 4. **Test Early, Test Often** - Write unit tests for core algorithms - Test physics in isolation (headless mode) - Automate testing with CI - **Bugs compound exponentially in simulation games** 5. **Manage Scope Ruthlessly** - **Minimum Viable Product (MVP):** One yard, 5 tracks, manual switching, basic physics - Defer 3D graphics, multiplayer, advanced AI - Ship early, iterate based on feedback - **Finished simple game > Unfinished complex game** ### First 3 Tasks (After Reading This Document) 1. **Decision: Choose technology stack** - Recommendation: Godot 4.2 + GDScript - Alternative: Unity + C# (if already familiar) - Document decision in `docs/adr/001-technology-stack.md` 2. **Setup: Initialize repository** - Follow Appendix B checklist - Push to GitHub (free private repos available) - Set up basic CI (GitHub Actions) 3. **Prototype: Straight track with moving train** - Goal: Single train moves from point A to point B - No graphics needed (can be console logs) - Validates: Physics integration, entity management - **Time budget: 1-2 days maximum** ### When to Ask for Help - **Architecture decisions:** "Should I use ECS or traditional OOP?" - **Algorithm selection:** "What's the best pathfinding for this graph structure?" - **Performance issues:** "Why is my frame rate dropping below 60 FPS?" - **Git problems:** "How do I resolve this merge conflict?" **Don't hesitate to ask Claude Code for help with any of these!** --- ## Conclusion Building a marshalling yard simulator is an ambitious but achievable project. The key to success is: 1. **Solid foundations** (architecture, version control, testing) 2. **Iterative development** (small features, frequent commits, milestones) 3. **Scope management** (MVP first, features later) 4. **Learning from others** (study OpenTTD, read Gaffer on Games) Your friend has identified the right tools (Git for iteration, branches for features, Claude Code for assistance). With disciplined workflow and clear milestones, this project can evolve from concept to playable prototype in 12-16 weeks. **The most important thing:** Start simple. A train moving on a straight track may seem boring, but it validates your entire architecture. Everything else builds on that foundation. Good luck, and happy coding! 🚂 --- **Document End** *For questions or clarifications, start a new Claude Code session with specific context about your current development phase.*