- Break monolithic records.rs into commands/, output/, parsing/ modules - Add lib.rs with shared types (AppError, Car, BestCarRecord, etc.) - Replace Arc<Mutex<HashMap>> with DashMap for concurrent access - Add unified AppError enum with thiserror - Add configurable CLI paths (--my-cars, --cars-db, --times-dir, --tracks-db) - Add serde deserialization for Car struct - Add CSV escaping for fields with commas/quotes/newlines - Fix track name splitting: split_once -> rsplit_once for _a handling - Add integration tests, proptest property-based tests, and unit tests - Add rustfmt.toml configuration - Update documentation (API, ARCHITECTURE, DATA_STRUCTURES, README)
9.3 KiB
Architecture Documentation
System Overview
The Top Drives Records Analyzer consists of a main analysis binary and a shared library working together to provide comprehensive car performance analysis for the Top Drives racing game.
Component Architecture
1. Shared Library (src/lib.rs)
Common types and utilities shared across the project.
Key Responsibilities
- Data Types: Defines
Car,BestCarRecord,AnalysisContext,CarCounts,MyCarsData,AppError - Track Name Translation: Converts track codes to human-readable names via
translate_track_name() - Track Type Translation: Converts condition codes to surface descriptions via
translate_track_type() - File Download: Async file download utility via
get_file() - User Data Loading: Parses
my_cars.jsonviaload_my_cars_data() - Car Comparison: Sorting comparator via
compare_cars() - Count Aggregation: Per-car win count computation via
compute_car_counts() - CSV Escaping: Safe CSV field formatting via
escape_csv_field()
Error Handling
All public functions return Result<T, AppError> where AppError is a unified error type:
AppError::Io— File system errorsAppError::Network— HTTP/download errorsAppError::Parse— JSON parsing errors with file contextAppError::MissingFile— Required data file not foundAppError::Poisoned— Mutex poisoning from panicked threads
2. Records Binary (src/records.rs)
Primary analysis engine that processes pre-downloaded JSON timing data.
Key Responsibilities
- Data Fetching: Downloads car and track databases from GitHub repository
- File Processing: Parses JSON timing files for each car configuration
- Performance Analysis: Compares timing data across RQ ranges and track conditions
- Collection Management: Identifies owned cars and optimization opportunities
- CSV Output: Writes analysis results to CSV files with proper escaping
Key Constants
MAX_RQ: u8 = 113— Maximum RQ level used for range analysisMIN_VALID_TIME: f64 = 1.2— Minimum valid track time thresholdTIME_TOLERANCE: f64 = 0.005— Tolerance for tie detection (5ms)TEST_BOWL_FACTOR: f64 = 2000.0— Multiplier for Test Bowl time inversionCONCURRENCY_LIMIT: usize = 20— Maximum concurrent tasks
Data Flow
GitHub Repository
↓
cars_repo.json + tracks_repo.json
↓
times/*.json (1000+ car files)
↓
JSON Parsing → BestCarRecord Analysis
↓
User Collection (my_cars.json)
↓
Output: CSV files / Terminal Results
Data Structures
Core Types
TripletKey
type TripletKey = (String, u8, String);
// ↓ ↓ ↓
// Track ID RQ Level Surface Condition
Purpose: Composite key for uniquely identifying track conditions Usage: HashMap key for storing best car records
Car
#[derive(Debug, Clone, Deserialize)]
struct Car {
rid: String, // Repository ID
brand: String, // Manufacturer
only_name: String, // Model name
year: u16, // Model year
rq: u8, // Rarity level
prize: bool, // Prize car status
guid: String, // Game GUID
}
BestCarRecord
struct BestCarRecord {
car_rid: String, // Car identifier
brand: String, // Manufacturer
only_name: String, // Model name
year: u16, // Year
rq: u8, // RQ level
config: String, // Tune config (111, 233, etc.)
time: f64, // Track time (seconds)
owned: bool, // User ownership status
prize: bool, // Prize car status
guid: String, // Game identifier
}
State Management
AnalysisContext
struct AnalysisContext {
best_cars: Arc<Mutex<HashMap<TripletKey, Vec<BestCarRecord>>>>,
}
Thread Safety: Protected by Mutex for concurrent access during parsing
Lifetime: Created in main(), passed through the call chain
Access: lock_best_cars() method returns Result with proper error handling
CarCounts
struct CarCounts {
by_rid: HashMap<String, (BestCarRecord, usize)>,
guid_to_rid: HashMap<String, String>,
car_map: HashMap<String, Car>,
car_list: Vec<Car>,
}
Purpose: Aggregated per-car win counts computed once from AnalysisContext
Usage: Shared across all output functions to avoid redundant computation
Algorithm Details
RQ Range Analysis
The system analyzes car performance across multiple RQ brackets:
- Base RQ: Car's natural rarity level
- RQ Range: Tests from car's RQ up to
MAX_RQ(113) - Cross-RQ Competition: Allows cars to compete above their base level
Example: A RQ 85 car might perform well at RQ 90-95 levels against higher-rated cars
Time Validation
Multiple validation layers ensure data quality:
- Minimum Time: Times below
MIN_VALID_TIME(1.2s) are discarded - Downvote Filter: Community-downvoted times are excluded
- Tie Detection: Times within
TIME_TOLERANCE(5ms) are considered equal - Special Cases: Test Bowl track uses inverted time calculation (
TEST_BOWL_FACTOR / time)
Concurrent Processing
Bounded Concurrency Pattern
- Task Spawning: One blocking task per car file via
tokio::task::spawn_blocking - Semaphore: Limits concurrent operations to
CONCURRENCY_LIMIT(20) - Error Isolation: Individual car failures don't stop batch processing
Synchronization
- Join Handles: Waits for all parsing tasks to complete
- Mutex Protection: Safe concurrent access to shared
AnalysisContext
File Organization
Input Files
├── cars_repo.json # Car database from repository
├── tracks_repo.json # Track definitions
├── track_names.json # Track code → name mappings (compiled in)
├── my_cars.json # User's car collection (game export)
└── times/ # Individual car timing files
├── Ferrari_F40_1987.json
├── McLaren_F1_1993.json
└── ...
Output Files
├── best.csv # All cars best on at least one track
├── very_best.csv # Cars best on ≥ min_count tracks
├── details.csv # Detailed timing data for all combinations
├── trash.csv # Owned cars safe to remove
├── wishlist.csv # Ideal wishlist (very_best unowned, capped)
├── add.csv # Cars to add to wishlist
└── remove.csv # Cars to remove from wishlist
Performance Characteristics
Records Module
- Startup Time: Fast (uses cached JSON files)
- Memory Usage: Moderate (loads all timing data into memory)
- Accuracy: High (comprehensive dataset)
- Update Frequency: Manual refresh required
- Concurrency: Bounded at 20 simultaneous operations via semaphore
Error Handling Strategy
Unified Error Type
All errors are represented as AppError:
- I/O Errors: File read/write failures
- Network Errors: Download failures with descriptive messages
- Parse Errors: JSON parsing failures with file name context
- Missing Files: Specific variant for absent data files
- Poisoned Mutex: Thread panic detection
Graceful Degradation
- Missing User Data:
load_my_cars_or_warn()returns empty data with a warning - Network Errors: Fetch errors logged but don't crash
- Parse Failures: Individual car file failures emit warnings and skip
User Feedback
- Progress Indication: Shows fetching/download progress
- Error Messages: Clear descriptions via
AppErrorDisplay impl - Warning Messages: Non-fatal issues reported via stderr
Extension Points
Adding New Tracks
- Update
tracks_repo.jsonwith new track definitions - Add track name mapping in
track_names.json - Add condition mappings in
translate_track_type()if needed
Adding New Analysis Types
- Create new output function(s) using
write_sorted_csv - Add command variant to the
Commandsenum - Add command handler in
main() - Update help text and documentation
Adding New Output Formats
- Create new output function(s) alongside existing CSV writers
- Reuse
CarCountsfor data access - Add CLI flag for format selection if needed
Security Considerations
Network Access
- HTTPS Only: All external requests use HTTPS
- Rate Limiting: Respects server limits with controlled concurrency
Local File Access
- Read Only: No modification of game files
- User Data: Only reads exported collection data
- No Persistence: Analysis results are output-only
Future Enhancements
Potential Improvements
- Caching Layer: Cache parsed results between runs
- Database Backend: Replace in-memory HashMap with persistent storage
- Configuration Files: External configuration for analysis parameters
- GUI Interface: Web-based interface for non-technical users
- Export Formats: Multiple output formats (JSON, XML, etc.)
- Module Split: Break
records.rsinto a module tree for better organization - Concurrent HashMap: Use
DashMapfor finer-grained locking
Scalability Considerations
- Pagination: Handle large datasets with pagination
- Incremental Updates: Only fetch changed data
- Compression: Compress large timing files
- Streaming: Process data without loading everything into memory