A modern horse racing simulation built with .NET Aspire, .NET 10 Web API, and Blazor.
Triple Derby is a management and simulation game where players breed, train, feed, and race virtual horses — each with unique genetics and performance characteristics. This modern rebuild uses .NET 10, Blazor, and .NET Aspire to orchestrate a clean, extensible architecture.
- Modern .NET 10 Web API for all gameplay operations
- Blazor Admin UI for testing, debugging, and management
- .NET Aspire orchestration for local infrastructure and future cloud deployment
- Message-driven breeding pipeline with a background worker (
TripleDerby.Services.Breeding/BreedingRequestProcessor) and transactional foal creation - Breeding engine implementing Punnett-style dominant/recessive genetics and a weighted mutation system
- Weighted color rarity selection and leg-type influenced racing behavior
- Advanced racing simulation with:
- Tick-based movement with modifier pipeline (Stats → Environment → Phase → Stamina → Random)
- Five distinct leg types with strategic advantages (phase-based timing and conditional bonuses)
- Stamina depletion system affecting performance over race distance
- Track condition modifiers (11 conditions from Fast to Frozen)
- Surface effects (Dirt, Turf, Artificial)
- Lane positioning and traffic detection
- Distributed caching for featured parent lists and performance-sensitive reads
- Message broker support (RabbitMQ) for message-driven workflows and background processing
- Training, feeding, and month-end lifecycle processing
- Health checks, graceful cancellation handling, and robust error handling for background workers and API
- Easily extensible to microservices, background workers, and cloud deployment
Included in the solution:
- .NET 10 Web Api
- Blazor Admin UI
- .NET Aspire orchestration
- Logging
- Global Exception Handling
- Cancellation Handling
- Health Checks
- CI Pipelines
- Unit Tests
- Worker Service(s) (e.g., Breeding & Racing Simulation)
- Distributed Caching (
IDistributedCacheAdapter) - Message Broker Support (RabbitMQ)
- Integration Tests
- Benchmark Tests
- Architectural Tests
graph TD
A{API Project} --> B[Core Project]
C[Infrastructure Project] --> B
A --> C
E[Shared Kernel] --> B
A --> E
C --> E
subgraph "API Layer"
A[TripleDerby.Api<br>• Controllers<br>• Configuration<br>• Program.cs]
end
subgraph "Core Layer"
B[TripleDerby.Core<br>• Entities<br>• Interfaces<br>• Domain Services]
end
subgraph "Shared Kernel"
E[TripleDerby.SharedKernel<br>• DTOs<br>• Base Classes<br>• Value Objects<br>• Common Interfaces<br>• Events]
end
subgraph "Infrastructure Layer"
C[TripleDerby.Infrastructure<br>• Repositories<br>• Http Clients<br>• Event Producers]
end
style A fill:#0078d7,stroke:#004578,stroke-width:1px,color:white
style B fill:#28a745,stroke:#115724,stroke-width:1px,color:white
style C fill:#ff9900,stroke:#cc7a00,stroke-width:1px,color:white
style E fill:#8a2be2,stroke:#5a189a,stroke-width:1px,color:white
flowchart TD
A[Player Action] --> B{Choose Activity}
B -->|Breed| C[Select Sire & Dam]
C --> D[Compute Genetics & Stats]
D --> E[Foal Created + Naming]
B -->|Train| F[Select Training Routine]
F --> G[Apply Stat Changes]
G --> H[Injury Check]
B -->|Feed| I[Pick Feed Type]
I --> J[Apply Happiness/Stat Effects]
B -->|Race| K[Select Track/Jockey/Plan]
K --> L[Race Simulation Engine]
L --> M[Outcome + Injury Check]
M --> N[Month-End Processing]
N --> O[Stipend + Reset AP + Reset Happiness]
classDiagram
class Horse {
Guid Id
string Name
Gender Gender
Color Color
LegType LegType
Stats GeneticDominant
Stats GeneticRecessive
Stats ActualStats
int Happiness
int Age
int RacesThisYear
}
class Stats {
int Speed
int Stamina
int Agility
int Durability
}
class BreedingService {
+Breed(sire, dam) Horse
+AssignColor()
+AssignGender()
+AssignLegType()
+ComputeGenetics()
+ApplyMutation()
+ComputeActualStats()
}
class TrainingService {
+Train(horse, routine)
+ApplyEffects()
+CheckInjury()
}
class FeedingService {
+Feed(horse, feedType)
}
class RacingService {
+SimulateRace(horse, conditions, plan)
+ComputeDistancePerTick()
+LaneModifiers()
+ConditionModifiers()
+OvertakeLogic()
}
class Stable {
Guid OwnerId
List~Horse~ Horses
}
Horse --> Stable : "belongs to"
BreedingService --> Horse
TrainingService --> Horse
FeedingService --> Horse
RacingService --> Horse
Horse --> Stats : "uses"
Breeding creates a new foal influenced by parent traits:
- Select CPU or stable-owned sire/dam
- Weighted color selection (with special colors)
- Leg type influences racing behavior
- Dominant/Recessive stats computed using Punnett-style quadrants
- Final step: player names the foal
sequenceDiagram
participant Player
participant BreedingController
participant BreedingService
participant Repository
participant MessagePublisher
participant MessageBus
participant BreedingRequestProcessor
participant RNG as RandomGenerator
participant NameGen as HorseNameGenerator
Player->>BreedingController: POST /api/breeding/requests (BreedRequest)
BreedingController->>BreedingService: Breed(request)
BreedingService->>Repository: Create BreedingRequest (Status=Pending)
Repository-->>BreedingService: BreedingRequest(created)
BreedingService->>MessagePublisher: Publish BreedingRequested(Event)
MessagePublisher->>MessageBus: Send BreedingRequested
MessageBus-->>BreedingRequestProcessor: Deliver BreedingRequested
BreedingRequestProcessor->>Repository: Find BreedingRequest(id)
Repository-->>BreedingRequestProcessor: BreedingRequest(entity)
BreedingRequestProcessor->>Repository: Update BreedingRequest (Status=InProgress) — claim
BreedingRequestProcessor->>Repository: Get Parent: Dam
Repository-->>BreedingRequestProcessor: Dam
BreedingRequestProcessor->>Repository: Get Parent: Sire
Repository-->>BreedingRequestProcessor: Sire
BreedingRequestProcessor->>RNG: GetRandomGender / LegType / Color / Stats
RNG-->>BreedingRequestProcessor: Random values
BreedingRequestProcessor->>NameGen: Generate()
NameGen-->>BreedingRequestProcessor: FoalName
Note right of Repository: ExecuteInTransaction:<br>- Create Foal<br>- Update parented counters<br>- Update BreedingRequest (FoalId, Status=Completed, ProcessedDate)
BreedingRequestProcessor->>Repository: Execute transaction (create foal & update)
Repository-->>BreedingRequestProcessor: Foal(created)
BreedingRequestProcessor->>MessagePublisher: Publish BreedingCompleted(Event)
MessagePublisher->>MessageBus: Send BreedingCompleted
MessageBus-->> Consumers: Deliver BreedingCompleted
alt Publish failure after commit
BreedingRequestProcessor->>Repository: Persist FailureReason (keep Status=Completed)
Repository-->>BreedingRequestProcessor: Ack
end
- May increase stats
- May increase happiness
- Reduced effect if recently injured
- Small chance of injury
- Affects happiness
- Can influence training effectiveness
- May add minor stat variance
Race outcome depends on:
- Track selection (surface, distance, conditions)
- Horse stats (Speed, Agility, Stamina, Durability)
- Leg type with distinct strategies:
- FrontRunner: Early race advantage (0-20%)
- StartDash: Opening quarter burst (0-25%)
- StretchRunner: Stretch run power (60-80%)
- LastSpurt: Closing kick (75-100%)
- RailRunner: Lane 1 bonus with clear path (conditional)
- Stamina system: Depletes based on distance, pace, and horse efficiency
- Environmental modifiers:
- Surface effects (Dirt, Turf, Artificial)
- 11 track conditions (Fast, Firm, Good, WetFast, Soft, Yielding, Muddy, Sloppy, Heavy, Frozen, Slow)
- Modifier pipeline: Base Speed × Stats × Environment × Phase × Stamina × Random
- Lane positioning and traffic detection
- Injury chance after each race
- Max 8 races per year, max 3-year racing career
- Player receives stipend
- Action points reset
- Happiness resets to 50
- .NET 10 SDK
- Docker Desktop (for Aspire dependencies)
- VS 2026, or VS Code + C# Dev Kit
dotnet run --project ./TripleDerby.AppHostAspire launches:
- TripleDerby API
- Blazor Admin UI
- Database or other configured services
- Aspire dashboard with logs + health checks
Seed the database with Racers with the following command:
cd .\tools\BreedingSeeder\
dotnet runFollow the on-screen prompts. Defaults are fine.
- Keep gameplay logic in domain services for clean, testable architecture
- Use deterministic RNG for reproducible breeding and racing tests
- Use DTOs to avoid leaking domain internals
- Future expansions:
- Breeding Worker
- Racing Simulation Worker
- Month-End Scheduler
- Consider messaging (RabbitMQ or Service Bus) for async workload distribution
The Core project is the center of the Clean Architecture design, and all other project dependencies should point toward it. As such, it has very few external dependencies. The Core project should include things like:
- Entities
- DTOs
- Interfaces
- Domain Services
Most of your application's dependencies on external resources should be implemented in classed defined in the Infrastructure project. These classed should implement interfaces defined in Core. The Infrastructure project should include things like:
- Http clients
- Repositories
- Event Hub Producer clients
- Service Bus clients
The entry point of the application is the Api project. The Api project should include things like:
- Program.cs
- Controllers
- Configurations
Test projects are organized based on the kind of test (unit, integration, benchmark, etc.).
Unit tests provide a way to verify and validate functionality of individual methods/components/features. This project contains example tests using xUnit.
Integration testing provides a way to ensure that an application's components function correctly at each level.
Architectural rules can be enforced using NetArchTest, a fluent API for .NET Standard that can enforce architectural rules within unit tests.
Benchmark testing is provided by BenchmarkDotNet, a powerful .NET library for creating and executing benchmark tests.
dotnet run --project ./tests/Api.Project.Template.Tests.Benchmark -c Release- Complete race engine visualization
- Expand Admin UI tooling
- Add stable management
- Worker/microservice separation
- In-game marketplace
- Public player UI