Vehicle car data and persistence¶
This document is the source of truth for owned vehicle data, persistence, loading, migration, and quarantine. Appearance and editor behavior are documented separately in Vehicle customization.
Source locations¶
src/Shared/DataTypes/DatastoreTypes.luaudefines canonicalCarDataand persisted quarantine metadata.src/Server/FrameworkRevamp/MainDatastore/init.luauowns the profile template and invokes vehicle migration before runtime consumers start.src-new/server/library/datastore/vehicle_datastore/persisted_vehicle_migration.luauconverts one historical record without mutating it.src-new/server/library/datastore/vehicle_datastore/persisted_vehicle_partition.luaupartitions all normal and quarantined records.src/Server/FrameworkRevamp/MainDatastore/VehicleDatastore/init.luauinstalls the partition and creates garage replication.src-new/server/library/vehicle_customization/migration.luauconverts legacy customization into the canonical customization schema.src-new/client/app/components/guis/VehicleJoinNotices/owns the count-only player notification shown after the main menu.
Core invariants¶
Everything in profile.Data.Cars is canonical and usable. Anything that cannot
become canonical is preserved unchanged in profile.Data.UnavailableCars and
retried on future loads.
Consequently:
- Runtime systems may treat every value in
CarsasDatastoreTypes.CarData. - Historical or malformed records never enter garage replication, spawning, customization, selling, rewards, or other runtime vehicle systems.
- A migration failure must not modify or discard the original record.
- Every migration input appears in exactly one output collection.
- Vehicle UIDs are unique across usable cars produced by a partition pass.
- An unavailable record is recoverable; quarantine is not deletion.
Do not broaden runtime CarData to include unavailable records. Unknown historical
data belongs at the persistence boundary so normal vehicle consumers only handle
supported runtime variation.
Profile collections¶
Cars contains canonical usable vehicles:
UnavailableCars contains exact raw records plus stable diagnostic metadata:
UnavailableCars: {
{
CarData = rawPersistedRecord,
Failure = {
reason = "missing-trim-definition",
vehicleId = 123,
schemaVersion = 1,
detail = nil,
},
},
}
Do not use CarsRemoving as quarantine. It represents valid cars participating in
the existing removal/accounting workflow, not records that cannot be interpreted.
Canonical CarData¶
CarData is a discriminated union of supported runtime architectures:
VehicleArchitecture is the persisted discriminator:
legacymust not carryAssembly,AssemblyItemsOwned, orAssemblyLiveriesOwned.assembly-v1must carry an assembly snapshot and both ownership ledgers.
A record saved before the discriminator existed is inferred as assembly-v1 when
it has an assembly snapshot and as legacy otherwise. Inference is permitted only
when the discriminator is missing. Once present, the discriminator is authoritative;
contradictory fields cause quarantine rather than silent reinterpretation.
New records select architecture explicitly. Ordinary reward, admin, event, and legacy dealership paths use the legacy generator. A dealership purchase with a resolved assembly uses the assembly-v1 generator.
Canonical customization¶
CarData.Customization is the persisted and replicated source of truth for vehicle
appearance. Canonical customization has the current SchemaVersion. Runtime UI and
server consumers read it directly from CarData; there is no separate customization
mirror or read model.
Customization without a schema version is legacy input and is converted during the vehicle migration pass. A non-nil unsupported schema version is quarantined instead of being interpreted as an older schema.
Canonical window tint is stored as the actual tint percentage and must remain within the applicable trim range (globally 5 through 95 percent). Defaults start at the minimum. Historical canonical values outside the range are clamped non-mutatingly during vehicle migration before runtime consumers receive them.
Customization purchases are server-authoritative. A legacy commit contains canonical customization. An assembly-v1 commit also contains the complete assembly root and accessory/livery ownership grants. All consequences are applied through one garage replica write.
Load and migration sequence¶
After ProfileService reconciliation and any legacy account transfer, vehicle loading runs synchronously before garage replication, global vehicle accounting, parts loading, and datastore patches. UID backfill is part of the successful partition path.
The partition pass consumes both Cars and the raw records inside
UnavailableCars. For each input:
- Validate that it is a table with a resolvable vehicle ID and trim definition.
- Infer or validate its vehicle architecture using local values only.
- Derive missing initial assembly ownership only during missing-discriminator inference.
- Reuse canonical customization or build canonical customization from legacy fields.
- Construct a replacement table without mutating the input.
- Generate a UID on the successful replacement when the historical record lacks one.
- Reject a later record that duplicates a UID already accepted in the same pass.
- Place the record into exactly one output collection.
Only after the complete pass finishes are profile.Data.Cars and
profile.Data.UnavailableCars replaced. Garage replication is then created directly
from canonical Cars.
Failure and quarantine¶
Migration returns structured failure data; it does not log or mutate persistence. The datastore boundary creates the quarantine wrapper, installs the completed partition, and reports unavailable records.
Current failure categories include:
- invalid persisted record;
- missing vehicle ID;
- missing trim definition;
- unknown vehicle architecture;
- legacy architecture carrying assembly data;
- assembly-v1 architecture missing required data;
- malformed assembly data;
- an unexpected customization or vehicle migration error;
- unsupported customization schema;
- duplicate vehicle UID.
Expected failures use stable reason identifiers. Unexpected caught errors may also
record a diagnostic detail string so operations can investigate them without
allowing one malformed vehicle to block the rest of the profile.
On failure, the player cannot currently use that vehicle. The raw record remains in
the profile and is attempted again on every subsequent load. If a removed definition
is restored or migration support is added, the next successful pass moves the
canonical replacement back into Cars with its original identity and data.
Player notification¶
Unavailable vehicles have no garage representation. After partitioning, the server
sets the player's UnavailableVehicleCount attribute to the number of quarantined
records. This count is the only quarantine information exposed to the client; raw
records, identities, failure reasons, and diagnostics remain server-only.
After the player closes the main menu for the first time, the vehicle join-notice coordinator displays a saved-data issue notice when the count is greater than zero. It states the number of affected vehicles, confirms that ownership and saved data were preserved, and asks the player to contact support for more details or if the issue persists. It does not identify vehicles or speculate about the cause.
The notice appears on every join while at least one record remains quarantined.
Dismissal through either the close button or OKAY is session-local and never changes
persisted quarantine data. If auto-sell refund information is also pending, that
notice is displayed first and the saved-data notice follows after dismissal; the two
must never overlap.
Future unavailable-vehicle UI must continue to avoid exposing quarantine records as
CarData or making normal vehicle actions accept them.
Compatibility rules¶
Persisted vehicle IDs, architecture values, schema versions, part IDs, and option IDs are durable data contracts. Removing or reassigning definitions requires an explicit saved-data compatibility plan.
When changing persisted vehicle data:
- Keep historical-field interpretation inside migration modules.
- Make the migration non-mutating and return a complete replacement or structured failure.
- Do not silently substitute a different vehicle or trim.
- Preserve failed raw input exactly.
- Add the new field to the profile template when it is a profile-level collection.
- Ensure old and quarantined records can be retried idempotently.
- Document the new invariant or compatibility rule here.
Legacy fields should be accompanied by a comment explaining their historical meaning and canonical interpretation. One unavailable vehicle must never prevent the rest of the player's profile from loading.