---
name: "skill_code_add_qcom_icd"
description: "Adds online Qualcomm ICD diag parsers in modem_proc_ext. Invoke when migrating offline QXDM/QCAT log extraction to modem-side parsing."
version: "0.4.13"
---

# Skill Code Add QCOM ICD

This skill is the workflow for converting an existing offline QXDM/QCAT log parser into an online Qualcomm ICD parser inside `modem_proc_ext`.

Use it when the target is a Qualcomm diag log such as `0xB884`, `0xB883`, `0xB887`, or another `0x....` log item, and the user wants the modem service to parse the ICD payload online.

Before any free-form implementation work, first read:

- `references/modem_data_registry/ICD_parsed.csv`
- `references/modem_data_registry/ICD_under_parse.csv`
- `references/modem_data_registry/ICD_under_review.csv`

These layered registries are the fast path for answering:

- whether the ICD is already online,
- whether the ICD has already entered parsing/review flow,
- which module currently owns related logs,
- whether the same ICD already fans out to multiple consumers,
- which module is the best first owner for a new parser.

## Scope Boundary

This skill defines the generalized workflow for migrating Qualcomm ICD logs into online modem-side parsing.

Keep these boundaries explicit:

- This file owns the common method: evidence requirements, packed-layout rules, parser safety rules, verification rules, and maintenance obligations.
- Log-specific semantic details, field-selection decisions, aggregation policy, and consumer-facing output shaping belong in `references/<log_id>/runs/` artifacts, not in this generic skill body.
- The final skill text should stay result-oriented and stable. Do not fill it with transient tool brainstorming, ad hoc debugging narratives, or one-off log-specific templates.

## Core Model

An existing offline parser can be a useful semantic reference, but it is not a prerequisite for online ICD development.

Offline parser:

- Input: QCAT/QXDM decoded text tree.
- Owner: `skill_log_filter_mdlog`.
- Typical code: `DataExtractor.py`.
- Output: CSV rows such as `NR_MAC_TX`, `NR_MAC_UL_SCHEDULE`, `NR_MAC_PDSCH`.
- Field access style: names such as `Records[i]`, `Carriers[0]`, `PUSCH Data[0]`, `TB Size`.

Online parser:

- Input: raw diag ICD payload from `diag_listener_log_ext_cb`.
- Owner: `bytedance_modem_service`.
- Typical code: `bytedance_diag_*_log.[ch]`.
- Output: protobuf fields sent through ByteDance QMI diag log indication or request response.
- Field access style: packed C structs, bit fields, pointer walking for variable-length arrays, explicit version gates.

The source of truth is the Qualcomm ICD/QCAT struct definition, not the CSV, not the QCAT display text, and not legacy comments.

## Local Reference Pattern

If the current codebase already contains one or more online ICD parsers, use them only as local wiring references.

Use existing local parsers for:

- checking how this codebase registers log IDs and routes dispatch,
- understanding local ownership boundaries such as listener registration, report owner, and protobuf output,
- comparing your independently implemented change against established module structure after the design is already clear.

Do not treat any existing parser, branch-local change, or historical commit as the source of truth for the new ICD layout. The actual source of truth remains the offline semantic contract plus the Qualcomm ICD/QCAT struct definition.

Key files:

- `bytedance_modem_service/src/bytedance_diag_log.c`
- `bytedance_modem_service/src/bytedance_diag_rf_log.c`
- `bytedance_modem_service/src/bytedance_diag_rf_log.h`
- `bytedance_modem_service/pb/BdDiagLog.proto`
- `bytedance_modem_service/src/bytedance_qmi_qcsi_svc.h`

A typical migration pattern is:

1. Add the log ID constant, for example `BD_LOG_NR5G_MAC_UL_PHYSICAL_CHANNEL_POWER_CONTROL`.
2. Add the log ID to the listener list in `bytedance_diag_log.c`.
3. Route the log ID in `diag_listener_log_ext_cb`.
4. Define packed ICD structs in the owning module header.
5. Add version-specific parsing, for example `0x30005`.
6. Validate top-level length, version, record count, carrier count, and variable array bounds.
7. Convert raw fields into service-level data.
8. Update the single owner state under the correct lock.
9. Report through the existing `diagLogInfo` protobuf path.

## Migration Inputs

Before writing code, collect these facts:

- Whether the target ICD is already listed in `references/modem_data_registry/ICD_parsed.csv`, `ICD_under_parse.csv`, or `ICD_under_review.csv`.
- Target log ID and QCAT name, for example `0xB883 NR5G MAC UL Physical Channel Schedule Report`.
- Supported ICD version values, for example `0x3001B`.
- Offline extractor function and CSV headers, if such tooling already exists.
- QCAT sample text for all expected channel or record variants.
- Qualcomm ICD/QCAT struct definition, including bit layout and variable-length arrays.
- Expected online output owner: `dataInfo`, `networkInfo`, `rfInfo`, `powerInfo`, `callInfo`, or a new protobuf message.
- Required aggregation window, if any. For power metrics this is often 1 second.
- Slot/subscription mapping from `diag_listener_get_subid_32bit`.
- Failure behavior for unsupported version, malformed payload, or oversized output.

## Input Readiness And User Guidance

When the user asks to implement `log XXX` online parsing but does not provide enough input evidence, do not guess the layout and do not jump straight into code.

Use this escalation order:

1. Search the local skill repo and `references/` first.
2. Reuse existing `<log_id>/input/` or `<log_id>/runs/` artifacts if they already cover the target version and variant scope.
3. Reuse local offline extractor logic, if available, to recover semantic field names and output candidates.
4. Only after the local repo has been checked, ask the user for the missing evidence.

Classify readiness explicitly:

- `implementation-ready`: semantic input, parser+hex evidence, target version, and output owner are all known.
- `research-ready`: semantic input exists, but wire evidence is incomplete; the skill should continue with semantic checklisting and wire-inference preparation.
- `blocked-for-wire-layout`: the target version or usable parser+hex evidence is still missing; implementation must pause after collecting what can be derived locally.

When evidence is missing, ask for the smallest useful set instead of a vague "please provide more info". Prefer this order:

1. target `log ID`, full QCAT name, and target `version`
2. `QCAT parser + hex dump` samples from the same captures
3. semantic source such as QCAT structure screenshots or an existing semantic table
4. offline parser output or CSV headers for the target log, if available
5. expected online consumer, output fields, and aggregation window

Preferred user-facing question style:

- `Please provide one or more QCAT parser + hex dump samples for <log_id> version <version>.`
- `If parser + hex is unavailable, provide the QCAT structure tree or screenshots so the semantic side can be locked first.`
- `If an offline extractor already exists, provide the function name, CSV headers, or one sample CSV row set for the same capture. This is helpful but not required for online ICD implementation.`
- `If the final online output is already known, state the owner module and whether the result is raw-event or aggregated output.`

If the user cannot provide the full set at once, continue iteratively:

- first lock the semantic side,
- then record the exact missing wire evidence,
- then resume packed-struct inference as soon as new parser+hex samples arrive.

The skill should always leave the task in one of these explicit states:

- ready to implement,
- ready to continue wire inference after one specific missing input arrives,
- or blocked on a clearly named missing artifact.

### Default Evidence Policy

For every future log ID handled by this skill, the default input set is:

1. `semantic table` input
2. `QCAT parser + hex dump` input

Treat both as mandatory by default when they are available in the repo or can be generated from the same sample set.

Use them with different responsibilities:

- `semantic table` defines the semantic tree, field names, ownership candidates, and missing-field checklist.
- `QCAT parser + hex dump` defines the wire layout, byte boundaries, record lengths, block lengths, and bit extraction evidence.

Do not use only one of them when both are available.

If one input is missing, state that explicitly and continue with the remaining evidence, but do not silently downgrade the workflow.

For local references, start from:

- `references/references.md`
- `references/modem_data_registry/ICD_parsed.csv`
- `references/modem_data_registry/ICD_under_parse.csv`
- `references/modem_data_registry/ICD_under_review.csv`
- `references/0xB884/input/0xB884_semantic_structure.md`
- `references/0xB883/input/0xB883_semantic_structure.md`
- `references/0xB887/input/0xB887_semantic_structure.md`
- `references/0xB884/input/0xB884_wire_inference_from_qcat_hex.md`
- `references/0xB884/runs/`
- `resources/80-VP457-6_REV_BA_Serial_Interface_Control_Document__ICD__for_Long_Term_Evolution__LTE_.pdf`

If a Feishu reference is needed, read only the target log struct section and record the exact version and field layout used.

For NR logs where no official ICD is available, treat locally archived QCAT structure screenshots as the semantic source of truth, then explicitly separate:

- `semantic evidence`: QCAT structure tree, screenshots, and any existing offline extractor fields
- `wire evidence`: packed bits, actual payload width, sample logs, verified parsing behavior

Do not silently treat semantic evidence as wire evidence.

If a `QCAT parser + hex dump` artifact is available, treat it as the primary wire-evidence input for NR logs and produce a `wire inference` note before writing final packed C structs.

If a `semantic table` already exists, use it as the semantic checklist instead of recreating the same screenshot summary.

If it does not exist yet, create either:

- a full semantic table, or
- a lightweight semantic checklist

before finalizing the packed mapping.

If a new sample corpus is added after an artifact already exists, update the existing run artifact instead of treating the new sample as an external note. The artifact must reflect the latest covered sample set, newly observed variants, and any narrowed or widened applicability boundary.

## Required Documentation Output

For every non-trivial log-ID study under this skill, produce a run artifact under:

- `references/<log_id>/runs/`

The default deliverable is:

- `<log_id>_semantic_vs_packed_struct_table.md`

This artifact must look like the current `0xB884` / `0xB887` style output and include:

1. evidence sources
2. evidence scope and covered sample variants
3. length rule or block-size rule
4. semantic-to-packed mapping table
5. confidence level per row
6. sample replay or self-verification section
7. current packed draft, including packed structs and locked-field accessor macros
8. parser draft or parser rules
9. remaining gaps and assumptions

### Mandatory Self-Validation Loop

Every field promoted from hypothesis to draft must go through the same closed loop:

1. state the current hypothesis or draft bit range,
2. replay the guess directly against hex dump bytes,
3. compare the replayed value against QCAT parser truth, or against an offline parser truth field when such tooling exists,
4. keep the field locked only if the replay matches across the stated corpus scope.

Record this loop in the run artifact. Do not claim a field as `High` confidence without a stated replay/check result.

For bit-range search work:

- Prefer the smallest contiguous little-endian bit range that explains the current truth set.
- If a wider zero-extended superset also matches, keep the smallest width as the current draft and note the wider candidates only as secondary observations.
- If a field is constant in the current corpus, keep it as unresolved even when one candidate layout looks plausible.
- If a field is `NA` in part of the corpus, state the replay scope explicitly instead of silently mixing numeric and `NA` rows.

This is the required reasoning shape:

- `guess`
- `packed/accessor draft`
- `hex replay`
- `parser truth comparison`
- `scope statement`

Do not skip the replay step and do not replace it with intuition, screenshot appearance, or hand-written bitfield narration.

### Accessor Style Contract

For fields that are already locked, the artifact must include an accessor draft in the same style as the current `0xB887` table:

- keep `evidence layer` and `implementation layer` separate:
  - `evidence layer`: bit range, replay scope, parser-truth comparison
  - `implementation layer`: getter macro or direct shift/mask expression ready for `.h/.c`
- keep outer layout in packed structs,
- expose locked inner fields through explicit getter macros or equally direct shift/mask helpers,
- keep enum/value mapping explicit at the parser boundary,
- prefer the repository's existing packed-struct style first. When stable code already models similar ICDs with `#pragma pack`, named fields, reserved gaps, and local bit extraction, follow that style before introducing a new representation.

The accessor draft is part of the artifact, not an optional appendix. It should be close enough to be pasted into a `.h` file with minimal reshaping.

For shipped modem-side parser code, apply this rule strictly:

- outer fixed-width headers may stay as packed structs when the byte layout is fully stable,
- prefer packed structs, named fields, reserved gaps, and local getter macros as the default shipped style when that matches the stable in-repo baseline,
- use `raw[N]` only when the inner block is still unresolved, or when existing packed-struct style cannot describe that block without adding more ambiguity than the baseline style,
- if you choose `raw[N]`, record why the stable packed-struct style is insufficient for that exact block and point to the conflicting wire property,
- do not ship parser-critical inner-field extraction through a representation that is less consistent with the stable repository baseline.
Do not split `current packed draft` and `current accessor draft` into two competing implementation sections. Put locked-field accessor macros directly inside the packed draft section, then keep the parser section focused on control flow only.

Final artifact writing rules:

- Treat files under `references/<log_id>/runs/` as final deliverables, not scratch notes.
- Keep log-specific field lists, output shaping decisions, and aggregation contracts in the corresponding `<log_id>` artifact instead of pushing them into this generic `SKILL.md`.
- Do not leave debug-stage or session-stage phrasing in the final artifact, such as:
  - `这次...`
  - `刚刚...`
  - `我先...`
  - `先这样...`
  - `看起来合理`
  - `目前先按...`
- Write conclusions in document voice, not conversational progress voice.
- Describe evidence, constraints, applicability, and unresolved gaps directly, without narrating the authoring process.
- If a section reflects a restricted evidence scope, state the scope explicitly as a stable constraint, for example:
  - `Current evidence in this artifact covers only ...`
  - `The following conclusion is valid under ...`
- Before finishing, do a final pass over the artifact and remove temporary wording, trial phrasing, and chat-style transitions.
- Keep the final write-up focused on evidence, layout, parser rules, applicability, and remaining gaps. Do not document temporary helper tools or draft-generation tactics as if they were part of the final result contract.

If the semantic side is still incomplete, add one of:

- `<log_id>_semantic_table.md`
- `<log_id>_semantic_checklist.md`

The packed mapping table remains the primary output artifact.

Implementation-stage source of packed/accessor draft:

- The default source is `references/<log_id>/runs/<log_id>_semantic_vs_packed_struct_table.md`.
- Agents should read the `current packed draft` and `accessor draft` sections from that artifact before editing `.h/.c`.
- If that artifact does not exist yet, create or update it first, then use it as the source of truth for packed-layout drafting.

## NR Packed Struct Inference Workflow

This section is the primary method for obtaining NR ICD packed C structs. Use it for multi-level or wire-uncertain NR logs such as nested record/carrier/channel layouts.

The goal is not to guess one struct that compiles. The goal is to converge on a packed draft that is explained by semantic evidence, replayed against raw bytes, and bounded by an explicit evidence scope.

### Inputs

Preferred inputs:

- QCAT semantic structure or equivalent semantic table
- QCAT parser output
- matching hex dump from the same samples
- offline extractor fields or CSV outputs, if such tooling exists
- multiple samples that cover different records, channels, carriers, optional blocks, and value ranges

Minimum acceptable starting point:

- one semantic source
- one parser+hex pair for the same version

If even this minimum is missing, use the `Input Readiness And User Guidance` section and stop before packed drafting.

### Workflow

1. Lock the semantic skeleton first.

   Build a semantic checklist that captures:

   - top-level record types
   - nested block names
   - per-block field names
   - optional or repeated blocks
   - fields that are output-relevant versus fields that are currently only structural

   At this stage, do not claim byte offsets or bit ranges.

2. Inventory the wire-evidence corpus.

   Record exactly which samples are available:

   - version
   - sample count
   - covered record variants
   - covered channel/carrier variants
   - whether each sample has both parser truth and hex bytes

   The corpus boundary is part of the result. Do not reason as if one sample represents all future layouts.

3. Establish top-level length rules before field inference.

   Determine:

   - top header length
   - system-time or common-prefix length
   - per-record fixed header length
   - per-carrier fixed header length
   - per-channel or per-block length
   - whether any block is optional, repeated, or version-gated

   Lock block-size rules before trying to lock inner bitfields.

4. Draft the outer packed layout first.

   Write only the outer framing that is already supported by evidence:

   - top-level packed header
   - fixed record headers
   - fixed carrier headers
   - fixed-length blocks
   - flexible-array entry points

   Keep uncertain inner fields as raw byte arrays until the wire evidence is strong enough to split them further.

5. Infer inner fields from replayable evidence, not from semantic naming alone.

   For each candidate field:

   - select the parser-truth target value,
   - search the corresponding bytes or bit ranges in the hex dump,
   - test little-endian width and placement hypotheses,
   - reject candidates that fail on other samples,
   - keep only the smallest draft that explains the current truth set.

   Treat field locking as a search problem with evidence, not as a formatting exercise.

6. Keep semantic evidence and wire evidence separate.

   Semantic evidence answers:

   - what the field means
   - which block it belongs to
   - whether it is repeated or optional

   Wire evidence answers:

   - where the field lives in bytes/bits
   - how wide it is
   - which block boundary owns it
   - whether the current hypothesis survives replay across the covered corpus

   Do not upgrade a semantic statement into a packed statement without wire proof.

7. Prefer unresolved-byte blocks only for unstable inner layouts.

   When the outer block length is known but the inner layout is still evolving:

    - keep the block as unresolved bytes only while the inner field layout is not yet locked,
   - expose locked fields through explicit getter macros or helpers,
   - split the block into nested packed structs only after the inner layout is sufficiently proven.

   This keeps parser control flow stable while the evidence still evolves.

8. Replay every locked field against raw bytes.

   For every field that moves from hypothesis to draft:

   - show the exact byte or bit range,
   - replay the numeric result from hex,
   - compare it with parser truth or offline truth,
   - state the corpus scope under which the field is considered locked.

   No field should be marked `High` confidence without replay evidence.

9. Iterate until the scope is explained, not until the first struct compiles.

   A draft is not done when the code builds. It is done when:

   - the outer length model is stable,
   - locked fields replay correctly across the stated corpus,
   - unresolved fields are explicitly left unresolved,
   - the parser draft can explain the intended online output contract.

10. Hand off to implementation conservatively.

   Only after the wire-inference artifact is strong enough should the code path be finalized in `.h/.c`.

   Carry over:

   - packed outer layout,
   - accessor helpers for locked inner fields,
   - parser rules and boundary checks,
   - confidence and scope notes for any still-partial area.

### Non-Negotiable Rules

- Do not derive final packed structs from QCAT semantic tree alone.
- Do not use one screenshot or one sample as the sole proof for a multi-level NR layout.
- Do not replace replay with intuition, visual alignment, or "looks consistent" reasoning.
- Do not overfit a draft to one corpus slice without stating the limited scope.
- Do not collapse unresolved inner fields into compiler bitfields just to make the code look complete.

## Offline To Online Mapping

Use this section only when an offline parser already exists for the target log. It is an optional bridge for reusing semantic output definitions, not a prerequisite for online ICD development.

For each target log:

1. Find the offline function in `DataExtractor.py`.
2. List every CSV output field.
3. For each field, identify the QCAT tree path.
4. Map the QCAT tree path back to the ICD struct field.
5. Decide whether the online output needs the raw field, a normalized value, or an aggregate.
6. Remove CSV-only artifacts such as `Record_Index` if they are not useful online.
7. Keep channel identity fields explicit, such as PUSCH, PUCCH, SRS, or PDSCH.

Examples from current offline logic:

- `0xB884` offline `get_nr_mac_tx()` maps `Channel Type`, `Transmit Power`, `PHR MTPL`, `TPC Adjustment`, and `Pathloss`.
- `0xB883` offline `get_nr_mac_ul_schedule()` maps UL schedule fields for `PUSCH`, `PUCCH`, and `SRS`.
- `0xB887` offline `get_nr_mac_pdsch()` maps PDSCH status fields such as `TB Size`, `MCS`, `Num Rbs`, `Num Layers`, `Mod Type`, `Num RX`, and `BWP ID`.

## Implementation Steps

Follow this order.

Implementation ownership rules:

- the owning module header is where the log ID, version enum, and packed struct belong,
- the owning module source is where `handle_xxx()` and version-specific parsers belong,
- the owner cache or accumulator is whichever state object is the real source of truth for the target output, such as `dataKpi`, `network`, `rf`, `power`, `call`, or another owner-specific cache.

1. Check the registry first.

Read `references/modem_data_registry/ICD_parsed.csv`, `ICD_under_parse.csv`, and `ICD_under_review.csv` before touching code.

Decide whether the target is:

- already registered and only needs extension,
- already represented by a nearby owner module,
- entirely new and needs a new row in the registry.

2. Pick the owning module.

For RF, cell, and power related ICD logs, prefer `bytedance_diag_rf_log.[ch]`.
For L2 throughput, RACH, call, or network behavior, prefer the existing matching `bytedance_diag_*_log.[ch]` owner.

If one ICD feeds multiple owners, keep one primary parser owner and fan out explicitly from `bytedance_diag_log.c`, following the existing pattern used by `0xB062`, `0xB88A`, `0xB0B4`, and `0xB860`.

After parsing, write results into the real owner model instead of inventing a temporary side path. Use the owner lock already used by that module.

3. Define log ID and supported versions.

Add constants near related log IDs. Do not duplicate the same log meaning across modules.

4. Define packed structs.

Use `#pragma pack(push, 1)` around ICD payload structs. Separate fixed headers from variable-length arrays.

If the struct ends with a flexible array such as `records[]`, compute the fixed header size with `offsetof(struct_type, records)` instead of `sizeof(struct_type)`.

For NR multi-level or otherwise non-trivial layouts, follow `NR Packed Struct Inference Workflow` before finalizing `.h/.c` structs. Treat packed drafting as an evidence-driven inference task, not as a one-shot translation from semantic tree to C layout.

Before editing `.h/.c`, read the current packed/accessor draft from `references/<log_id>/runs/<log_id>_semantic_vs_packed_struct_table.md`. If the artifact is missing, incomplete, or older than the available evidence corpus, update the artifact first and only then materialize the draft into code.

Packed C struct usage constraints:

- Use packed structs first for stable outer framing only: top header, fixed record header, fixed carrier header, fixed block header, and flexible-array entry points.
- If an inner block length is known but its internal field layout is not fully locked, keep that block as unresolved bytes instead of forcing a speculative nested struct.
- Expose locked inner fields through explicit getter macros or shift/mask helpers. The packed struct carries layout ownership; the accessor carries field-extraction ownership.
- Do not encode unresolved or weak-evidence inner fields directly as compiler-dependent C bitfields just to make the struct look complete.
- Do not replace an established packed-struct/reserved-field house style with `raw[N]` unless you can prove the stable style is insufficient for the target block.
- Split an inner raw block into nested packed structs only after the byte boundary, bit range, and replay scope are already stable across the stated corpus.
- When one version changes only an inner block layout, keep the outer packed framing reusable and isolate the changed extraction logic in version-specific accessors or parser functions.

The packed C struct is a bounded wire-layout model, not a mirror of the full semantic tree. It should represent only the portion of layout that is already justified by evidence.

5. Register the log.

Add the log ID to `log_id[]`, then route it in `diag_listener_log_ext_cb`.

Adding only the handler is not enough. Missing either side creates a silent dead path.

6. Add a minimal dispatcher.

The generic handler should validate:

- `data != NULL`
- `len >= offsetof(top_struct, variable_array)`
- supported version
- non-zero and bounded record count

Then call a version-specific parser.

Keep the validation order stable:

- `data != NULL`
- `len >= header_len`
- cast to top-level struct
- read `version` and `count`
- validate `slotId`
- validate `version`
- validate `count`
- enter version-specific parsing

Do not read `log_ptr->version` or `log_ptr->num_records` before validating that `len` covers the fixed header.

Keep one generic `handle_xxx()` for entry validation and version dispatch, then route into `handle_xxx_vN()` style functions. Do not mix multiple version layouts in one large parser body unless the wire layout is truly identical.

7. Parse variable arrays with pointer walking.

Maintain `ptr` and `data_end`. Before every dereference, check `ptr + sizeof(...) <= data_end`. Bound all count fields with explicit maximums.

When the layout is single-level and fixed-size after the header, also compare the advertised count with the count implied by the real payload length. When the layout is nested, treat `ptr/data_end` as the source of truth for each parsing layer.

8. Normalize values at the boundary.

Convert raw ICD units once, at the parser boundary. Preserve the online output contract in one owner model. For power values, be explicit whether the unit is `dBm * 10`, `dB * 10`, bytes, RB count, layer count, enum value, or string.

9. Update shared state under the owner lock.

Use the existing lock for the target report object. Do not mutate `diagLogInfo` from multiple places without one clear owner.

10. Report via protobuf.

Add protobuf fields only after the owner and output contract are clear. Check `QMI_BYTEDANCE_CMD_MAX_LEN_V01` impact before adding repeated fields.

11. Clean state deterministically.

For accumulated data, clear only after reporting or when the source-of-truth state changes, such as RAT or band change. Do not silently keep stale slot data.

12. Constrain log printing by stage and frequency.

During parser bring-up and field validation, prefer `DBG_LOGE_*` for per-record, per-channel, per-RB, or other high-frequency non-final debug prints. This is the default path for observing intermediate decode state.

Do not use `BD_DBG_LOG` as the default parser bring-up vehicle. In the current `bytedance_modem_service` codebase, `BD_DBG_LOG` is a historical duplicate wrapper with a different gate and limited legacy call sites; it is not the source of truth for high-frequency ICD field validation.

Reserve `BD_MSG_HIGH` for low-frequency and stable milestones only, such as:

- parser entry or one-shot route confirmation,
- unsupported version or malformed payload summary,
- 1-second or other final aggregate flush,
- owner-state transition or final indication/report boundary.

Do not use `BD_MSG_HIGH` as the default vehicle for high-frequency raw field dumps. Avoid emitting one `BD_MSG_HIGH` line for every parsed record or channel when the same information is only needed during temporary debug.

If temporary per-record visibility is needed, add scoped `DBG_LOGE_*` prints near the parser boundary and remove or downgrade them once the field mapping is validated.

## Parser Safety Checklist

Every online ICD parser must satisfy these invariants:

- Input pointer is non-null before use.
- Payload length is checked before casting.
- Version is checked before field interpretation.
- Every variable-length count has an upper bound.
- Pointer movement never passes `data_end`.
- Slot ID is validated before indexing slot arrays.
- Unsupported channel or enum values are ignored or logged explicitly.
- Shared state is updated under the correct lock.
- Partial parse failure stops the affected packet without corrupting previous valid state.
- Output units are documented and match the protobuf/API contract.

## Verification Workflow

When offline output exists, use it as one semantic cross-check. Otherwise, rely on QCAT parser truth, semantic structure, replay evidence, and the stated output contract.

1. Collect a QXDM log containing the target ICD log.
2. If an offline extractor exists, generate offline CSV with `skill_log_filter_mdlog`.
3. Build the online parser with temporary structured logs for each parsed record.
4. Run the same scenario on device.
5. Compare online debug logs/protobuf output against available truth sources, preferably QCAT parser truth and, when available, offline CSV, by timestamp, slot, channel, and key fields.
6. Validate boundary behavior with empty record count, unsupported version, maximum count, and truncated payload if test data is available.

Logging rules during verification:

- In active debug stage, temporarily enable `DBG` so that `DBG_LOGE_*` instrumentation is visible.
- Do not assume enabling `BYTEDANCE_MODEM_DEBUG_MODE` alone makes `DBG_LOGE_*` visible unless the local codebase has explicitly tied `DBG` to that gate.
- Keep the `DBG_LOGE_*` payload structured and field-oriented, so that it can be compared directly against offline CSV or QCAT truth.
- Do not permanently flip high-frequency parser traces to `BD_MSG_HIGH` just to make them visible.
- Do not switch parser field dumps to `BD_DBG_LOG` just because `DBG_LOGE_*` is currently gated off; fix the actual `DBG` gate instead.
- After field mapping and replay checks are complete, restore the default low-noise state and keep only low-frequency `BD_MSG_HIGH` summaries that are still useful in long-term maintenance.

For power or throughput aggregation, compare after applying the same aggregation policy. Do not compare per-record offline rows directly against a 1-second online aggregate unless the aggregation has been reproduced.

Before declaring the research or migration-ready phase complete, verify that:

1. the semantic input has been traversed,
2. the parser+hex input has been traversed,
3. a `<log_id>_semantic_vs_packed_struct_table.md` run artifact exists,
4. the artifact is sufficient to drive packed C struct drafting and parser implementation.

## Minimal Development Checklist

Use this as the generalized form of the wiki's RF checklist. Replace `owner module` with the actual chosen owner for the target ICD.

- [ ] The owner module header defines the new `BD_LOG_XXX`.
- [ ] The owner module header defines supported version enums.
- [ ] The owner module header defines packed ICD structs.
- [ ] `bytedance_diag_log.c` registers the new log in `log_id[]`.
- [ ] `diag_listener_log_ext_cb()` routes the log into the right handler.
- [ ] The owner module source implements `handle_xxx()`.
- [ ] `handle_xxx()` checks `data == NULL`.
- [ ] `handle_xxx()` validates `header_len` or fixed-size length before reading cast fields.
- [ ] `handle_xxx()` validates `slotId` before indexing slot-based state.
- [ ] `handle_xxx()` validates `version`.
- [ ] Variable-length payloads validate advertised counts against real payload length or `ptr/data_end`.
- [ ] Every record or nested block is bounds-checked before dereference.
- [ ] Parsed fields are written into the correct owner state under the correct lock.
- [ ] New versions are isolated in `handle_xxx_vN()` style functions without breaking older versions.

## Registry Maintenance

This is mandatory whenever the skill leads to a code change.

Before code changes:

1. Check `references/modem_data_registry/ICD_parsed.csv`, `ICD_under_parse.csv`, and `ICD_under_review.csv`.
2. Decide whether the target is existing, adjacent to an existing owner, or truly new.
3. State the intended owner module using downstream output ownership, not just ICD name similarity.

After code changes:

1. Update the appropriate layered registry under `references/modem_data_registry/` in the same change set.
2. If new reference notes were added, update `references/references.md`.
3. If the workflow, module-placement rule, or maintenance rule changed, update this `SKILL.md` too.

Do not consider the task complete if the code changed but the registry and workflow documentation were left stale.

## Common Pitfalls

- Treating QCAT text field order as the binary layout. Use ICD struct layout as the source of truth.
- Copying CSV-only columns into protobuf without a real online consumer.
- Adding repeated protobuf fields without checking QMI max length.
- Forgetting `diag_listener_get_subid_32bit` maps subscription to zero-based slot by `subid - 1`.
- Using `0` as an invalid sentinel for metrics where `0` is a valid value.
- Averaging dBm directly when a power average should be done in linear domain.
- Trusting `sizeof(struct_with_flexible_array)` for the whole payload.
- Updating shared state without lock ownership clarity.
- Adding a parser but forgetting to add the log ID to `log_id[]`.
- Adding a log ID but forgetting to route it in `diag_listener_log_ext_cb`.
- Leaving `DBG` disabled during parser bring-up, then compensating by turning high-frequency field dumps into permanent `BD_MSG_HIGH` noise.
- Mistaking `BYTEDANCE_MODEM_DEBUG_MODE` or `BD_DBG_LOG` for the default gate/path of `DBG_LOGE_*` without verifying the local macro definitions first.
