- Add improve command to all command tables and usage sections - Add normalized_config field to BestCarRecord struct documentation - Update CarCounts.by_rid type from String to (String, String) - Update CarCounts.guid_to_rid type to HashMap<String, HashSet<(String, String)>> - Add owned_configs field to MyCarsData and CarCounts docs - Add normalize_to_final_build() to API docs - Update CSV format examples to include FinalBuild column - Update my_cars.json structure with engineMajor/weightMajor/chassisMajor - Update timing file example with v2c_ version prefix - Add version prefix stripping to data transformation docs - Update CHANGELOG.md with Unreleased section - Fix compare_records description to include normalized config sorting
13 KiB
JSON Data Structures Documentation
Overview
This document describes the JSON data structures used by the Top Drives Records Analyzer, including their purpose, format, and usage within the application.
Core Data Files
1. Car Database (cars_repo.json)
Purpose: Master database of all cars in Top Drives with their metadata.
Source: Downloaded from https://github.com/TiagoXavi/topdrives-records/raw/refs/heads/main/src/database/cars_final.json
Structure:
[
{
"rid": "Ferrari_F40_1987",
"brand": "Ferrari",
"onlyName": "F40",
"year": 1987,
"rq": 95,
"prize": false,
"guid": "card_ferrari_f40_1987"
}
]
Field Descriptions:
| Field | Type | Description |
|---|---|---|
rid |
String | Repository identifier (unique) |
brand |
String | Car manufacturer/brand name |
onlyName |
String | Car model name (without brand) |
year |
Number | Model year |
rq |
Number | Rarity level (10-113) |
prize |
Boolean | Whether car is prize-only |
guid |
String | Game-specific card identifier |
Usage in Application:
- Loaded by
get_car_list()function - Used to identify cars for timing file processing
- Provides metadata for analysis output
2. User's Car Collection (my_cars.json)
Purpose: Contains the user's owned cars exported from the Top Drives game.
Source: Exported from Top Drives game (Settings → Account → Export Data)
Structure:
{
"playerDeck": [
{
"cardId": "card_ferrari_f40_1987",
"locked": true,
"engineMajor": 3,
"weightMajor": 2,
"chassisMajor": 3
},
{
"cardId": "card_mclaren_f1_1993",
"locked": false,
"engineMajor": 2,
"weightMajor": 3,
"chassisMajor": 3
}
]
}
Field Descriptions:
| Field | Type | Description |
|---|---|---|
playerDeck |
Array | List of user's cards |
cardId |
String | Unique card identifier |
locked |
Boolean | Whether card is owned (true = owned) |
engineMajor |
Number | Engine tune level (optional) |
weightMajor |
Number | Weight tune level (optional) |
chassisMajor |
Number | Chassis tune level (optional) |
Processing Logic:
- Only
locked: truecards are considered owned cardIdvalues are matched against car database GUIDs- Used to mark cars as "owned" in analysis results
- Tune config extracted as
"{engineMajor}{weightMajor}{chassisMajor}"(e.g.,"323") - Cards with incomplete tune data are skipped for the improve command with a warning
3. Individual Car Timing Files (times/{rid}.json)
Purpose: Contains detailed timing data for a specific car across all tracks and conditions.
Source: Downloaded from https://api.topdrivesrecords.com/car/{rid}
Example Structure:
{
"users": ["Player1", "Player2"],
"data": {
"v2c_111": {
"times": {
"laguna_a00": {
"t": 112.45,
"u": "Player1"
},
"monaco_a00": {
"t": 45.23,
"u": "Player2",
"down": ["Player3"]
}
},
"info": {
"topSpeed": "201",
"acel": "3.2",
"hand": "92"
}
}
},
"rid": "Ferrari_F40_1987"
}
Field Descriptions:
| Field | Type | Description |
|---|---|---|
users |
Array | List of players who submitted times |
data |
Object | Timing data organized by configuration |
rid |
String | Car repository identifier |
Configuration Structure (data.{config}):
| Field | Type | Description |
|---|---|---|
times |
Object | Track timing data |
info |
Object | Car performance stats |
Track Timing Structure (times.{track}_{condition}):
| Field | Type | Description |
|---|---|---|
t |
Number | Time in seconds |
u |
String | Username who submitted the time |
down |
Array | Usernames who downvoted this time (optional) |
d |
String | Date/time of submission (optional) |
Usage in Application:
- Processed by
parse_car_times()function - Each track entry becomes a
BestCarRecord - Downvoted times are filtered out for quality
- Times below 1.2 seconds are considered invalid
4. Tracks Database (tracks_repo.json)
Purpose: Defines all tracks and their properties in Top Drives.
Source: Downloaded from https://github.com/TiagoXavi/topdrives-records/raw/refs/heads/main/src/database/tracks_repo.json
Example Structure:
[
{
"id": 1,
"name": "Laguna Seca",
"conditions": ["00", "01", "10"],
"type": "circuit"
}
]
Field Descriptions:
| Field | Type | Description |
|---|---|---|
id |
Number | Unique track identifier |
name |
String | Human-readable track name |
conditions |
Array | Supported surface conditions |
type |
String | Track category (circuit, drag, rally, etc.) |
Usage in Application:
- Provides track name mappings for output
- Used for track-specific analysis queries
- Supports condition filtering
5. Track Names Configuration (track_names.json)
Purpose: Maps internal track codes to human-readable track names.
Source: Included in the repository, loaded at compile time via include_str!
Structure:
{
"laguna": "Laguna Seca",
"monaco": "Monaco Streets",
"testBowl": "Test Bowl",
"dealsGap": "Deals Gap"
}
Field Descriptions:
| Field | Type | Description |
|---|---|---|
| key | String | Internal track code (e.g., "laguna") |
| value | String | Human-readable track name (e.g., "Laguna Seca") |
Usage in Application:
- Loaded by
translate_track_name()insrc/translate.rs - Compiled into the binary at build time
- Fallback: Returns original track code if no mapping found
Application Constants
MAX_RQ
const MAX_RQ: u8 = 113;
Purpose: Defines the maximum RQ level used in analysis range calculations.
Usage: When evaluating car performance, each car is tested from its base RQ up to MAX_RQ. This replaces the former magic number 115, providing a named constant for clarity and easy adjustment.
Location: Defined in src/records.rs
Data Processing Pipeline
1. Data Acquisition Phase
Files Downloaded:
cars_repo.json- Complete car databasetracks_repo.json- Track definitionstimes/{rid}.json- Individual car timing data (1000+ files)
Validation:
- HTTP response codes checked
- JSON parsing errors handled gracefully
- File corruption detection
2. Data Parsing Phase
Car Database Processing:
// Parse cars_repo.json — uses serde derive for deserialization
let cars: Vec<Car> = serde_json::from_str(&content)?;
// Falls back to manual Value-based parsing if serde deserialization fails
// (for backward compatibility with older JSON formats)
Timing Data Processing:
// Parse individual car timing file
let json_data: Value = serde_json::from_str(&file_content)?;
if let Some(configurations) = json_data["data"].as_object() {
for (raw_config, entry) in configurations {
// Strip version prefix (e.g., "v2c_" from "v2c_233")
let config = raw_config
.strip_prefix("v2c_")
.or_else(|| raw_config.strip_prefix("v1c_"))
.unwrap_or(raw_config);
if let Some(times) = entry["times"].as_object() {
for (track_id, details) in times {
// Process each track time
}
}
}
}
3. Data Transformation Phase
Track ID Parsing:
- Input:
"laguna_a00"(track + condition) - Process: Split on
"_a"delimiter - Output:
("laguna", "00")(track name, condition code)
Track Name Translation:
- Track codes are looked up in
track_names.json(compiled into binary) - Example:
"laguna"→"Laguna Seca" - Fallback: Unknown codes returned as-is
Condition Code Translation:
"00"→"Dry""01"→"Wet""10"→"Dirt""11"→"Dirt Wet"
Time Validation:
- Check for downvotes:
details["down"].as_array() - Validate time range:
time > 1.2 - Special handling: Test Bowl inversion
2000.0 / time
4. Data Aggregation Phase
Best Car Records Creation:
BestCarRecord {
car_rid: car.rid.clone(),
brand: car.brand.clone(),
only_name: car.only_name.clone(),
year: car.year,
rq: car.rq,
config: config.clone(),
normalized_config: normalize_to_final_build(&config),
time,
owned: my_cars.contains(&car.guid),
prize: car.prize,
guid: car.guid.clone(),
}
RQ Range Analysis:
- Test each car from its base RQ up to
MAX_RQ(113) - Compare against existing best times using
DashMapentry API - Update records if better performance found
- Uses
DashMapfor fine-grained concurrent access (no global lock)
Data Quality Measures
Validation Rules
-
Time Thresholds:
- Minimum time: 1.2 seconds
- Test Bowl: Inverted calculation (2000.0 / time)
-
Community Validation:
- Downvoted times excluded
- Multiple submissions required for reliability
-
Data Consistency:
- Track name normalization
- Condition code standardization
- Car identifier validation
Error Handling
Network Errors:
- Failed downloads logged but don't stop processing
- Existing cached data used as fallback
- Clear error messages for user
Parse Errors:
- Malformed JSON handled gracefully
- Missing fields use default values
- Invalid times filtered out
File System Errors:
- Missing files reported clearly
- Permission issues identified
- Corrupt files detected and skipped
Performance Considerations
Memory Usage
Large Data Structures:
AnalysisContext.best_cars(DashMap<TripletKey, Vec<BestCarRecord>>): Contains all best records across RQ ranges- Individual timing files: Loaded and parsed sequentially via
spawn_blocking - User collection: Small, loaded once
Optimization Strategies:
- Concurrent processing of car files with bounded concurrency (semaphore limit of 20)
- Rate-limited downloads (20 concurrent max)
- Streaming JSON parsing where possible
Processing Speed
Bottlenecks:
- Network I/O: 1000+ small files downloaded
- JSON Parsing: Each timing file parsed individually
- HashMap Operations: Frequent insertions with RQ range checking
Optimizations Applied:
- Bounded Concurrency: Semaphore limits concurrent operations to 20
- Connection Pooling: Reuse HTTP connections
- Early Filtering: Invalid times rejected quickly
Data Evolution
Version Compatibility
Backwards Compatibility:
- Old timing files still parseable
- New fields added gracefully
- Deprecated fields handled
Schema Evolution:
- New track additions supported
- Condition code extensions possible
- Car metadata fields expandable
Update Strategy
Incremental Updates:
- Only changed cars need re-downloading
- Track database updated periodically
- User collection refreshed as needed
Data Freshness:
- Community timing data updates frequently
- Car database relatively stable
- Track definitions rarely change
Troubleshooting Data Issues
Common Problems
Missing Timing Data:
- Symptom: Cars show no performance data
- Cause: Timing file not downloaded or corrupted
- Solution: Re-run
fetchcommand
Incorrect Ownership:
- Symptom: Wrong cars marked as owned/missing
- Cause: Outdated
my_cars.jsonexport - Solution: Re-export from Top Drives game
Track Name Issues:
- Symptom: Unknown track codes in output
- Cause: Missing mapping in
track_names.json - Solution: Add new mapping to
track_names.json
Data Validation Tools
Integrity Checks:
- File Count: Verify expected number of timing files
- JSON Validity: Parse all files for syntax errors
- Reference Integrity: Cross-check car and track references
Diagnostic Commands:
# Check for missing timing files
ls times/ | wc -l # Should be 1000+
# Validate JSON syntax
find times/ -name "*.json" -exec jq empty {} \;
# Check for common issues
grep -r "No RID" times/
Future Data Structure Enhancements
Potential Improvements
- Compression: Gzip timing files to reduce storage
- Indexing: Add database indexes for faster queries
- Caching: Cache parsed data to avoid re-processing
- Incremental Updates: Only download changed files
Schema Extensions
Additional Fields:
- Car weight and dimensions
- Performance curves
- Weather condition effects
- Driver skill impact
New Analysis Types:
- Historical performance trends
- Meta change impact analysis
- Community submission quality metrics
- Regional performance differences