Live vs. known
Why the BYD framework reports ~21k catalog entries but your car only exposes ~9k live features — the dedup, the device-type coverage, and the write-only commands that account for the gap.
TL;DR. The framework catalog reports ~21k entries because nested-class constants are stored twice (bare + prefixed), doubling ~10–11k unique constants. The registry then probes each name across seven device-types, drops sentinels, and de-duplicates first-wins. On a Leopard 8 the live count settles around 9k. The gap is expected; it's where write-only commands, unprobed subsystems, and unimplemented features fall.
This is the single most common question — "BYD says 21k, my car shows 9k, what's broken?" Nothing's broken. The numbers measure different things.
What the two numbers mean
| Number | What it counts | Source |
|---|---|---|
| ~21,000 | Keys in BydAutoFeatureIdsCatalog.byName. | Reflection of BYDAutoFeatureIds + every nested class, with each nested constant stored under both its prefixed and bare name. |
| ~9,000 | Live registry entries on this car. | AutoCarRegistry.entries.size — names that returned a non-sentinel value under at least one of seven WARM_DT device-types. |
The 21k is "BYD's universe of names" — the framework's vocabulary. The 9k is "names that are actually bound on this hardware right now."
The three reductions
catalog (bare + prefixed) ~21,000
↳ dedup to unique constants ~10,500 (-50%)
↳ × WARM_DT subsystem coverage ~9,500 (-10% from unprobed dts)
↳ − write-only / command keys ~9,000 (-5% sentinel under read)1. Bare + prefixed storage doubles the catalog
BydAutoFeatureIdsCatalog stores nested-class constants twice:
Ac.AC_POWER_STATE→0x60000001(prefixed)AC_POWER_STATE→0x60000001(bare, for back-compat)
That's intentional — see Catalog — and
absorbed by the registry's first-wins dedup on read. So out of
the 21k catalog keys, only ~10.5k correspond to distinct
(framework int) values.
2. WARM_DT covers seven device-types, not all of them
BYD's framework partitions features by device-type. The registry today registers push devices for seven dts (1000, 1001, 1023, 1038, 1040, 1041, 1045). Constants scoped to other dts — ADAS (1002), mirrors (1003), energy (1018) — are invisible to the probe. They exist in the catalog but read as sentinel under the WARM_DT walk.
This drops another ~10 % off the ceiling.
3. Write-only command keys read as sentinel
A meaningful fraction of the catalog encodes commands, not state:
Door.DOOR_LOCK_COMMAND_AREA_LEFT_FRONT— issuing a write here locks the door. Reading it returns-10011because there's no underlying CAN signal carrying state-of-command. The locked state lives elsewhere underBodywork.*.Ac.AC_POWER_MODE_SET— issuing a write here changes mode. Reading returns-10011. The current mode is exposed underAc.AC_POWER_STATEinstead.
The registry's sentinel filter correctly drops these — they're
not "live readable values," they're write addresses. They remain
available as write targets through fast_actions in the textproto
registry; the SDK invokes them via api.invoke('door.lock').
This drops the last few percent.
Trim variation
| Trim | Live count | Notes |
|---|---|---|
| Han L4 | ~7,500 | Smaller subsystem footprint; no premium audio, fewer driver-assist sensors. |
| Tang L4 | ~8,200 | Mid-size SUV; tire pressure cluster + sunroof add a few hundred. |
| Leopard 8 Flagship | ~9,500 | Off-road sensors, full lighting suite, dual-screen cluster. |
| Yangwang U8 | ~10,200 | Only when ADAS dt (1002) is in WARM_DT — currently off by default. |
Trim variation comes from which subsystems exist on the hardware, not from the SDK choosing to expose less. The same probe code runs on every trim; the framework simply returns more sentinels on trims with fewer subsystems.
Diagnostic recipe
final api = ref.watch(carRegistryClientProvider);
final live = await api.liveFeatures(); // Map<String, int>
final known = await api.allCatalogNames(); // Iterable<String>
final notLive = known.toSet()..removeAll(live.keys);
// Names in catalog but not live on this car.notLive will contain ~12k names on a Leopard 8 — that's the
expected gap. Don't try to "fix" it.
When the gap is a bug
Two cases where the gap deserves investigation:
| Symptom | What it suggests |
|---|---|
| Live count is far below the trim's typical range (e.g. Leopard 8 at 4k) | BydPushDevice failed to construct for several WARM_DTs. Check inAppPush.stats().devicesRegistered. |
| Live count drops between boots on the same trim | A ROM update changed the framework. Cache signature mismatch should have triggered a re-probe; verify in logs (AutoCarRegistry: cache invalidated). |
Anything within ~±500 of the trim norm is normal trim variation.
What you should NOT do
- Don't read from the framework directly to bypass the registry. You'd hit the same sentinels. The framework doesn't have more data; it just exposes it through the same API the registry uses.
- Don't disable the sentinel filter to "see more entries." Sentinels are not data. Letting them through pollutes the registry with phantom values that never update.
- Don't expect future DiLink versions to close the gap. Each release moves the live count by ~±200, not by thousands. The bare+prefixed dedup and the write-only commands are structural — they aren't going away.
Auto-discover
How AutoCarRegistry turns the BYD framework catalog into a per-car live feature index — once at boot, push-driven thereafter, with zero per-trim curation.
Bridge surfaces
The five method-channel verbs and the registry EventChannel that the Dart SDK consumes — wire shapes, threading, error envelopes.