<!-- Auto-generated guidance for AI coding agents. Keep concise and concrete. -->
# Copilot / AI Agent Instructions

Purpose: help an AI coding agent be immediately productive in this repo by documenting project shape,
important patterns, and common developer workflows observed in the codebase.

- **Top-level structure**: core application is in `pymsc/` (Core + GUI). A lightweight demo monitor lives in `demo_bus_monitor/`. Legacy code and examples are in `_old/`.

- **How the system is split**:
  - `pymsc/core`: application logic and networking (see `app_controller.py`). It runs the scheduler, receiver, and exposes `go()` / `stop_sending()` semantics.
  - `pymsc/gui`: Tkinter GUI (`main_window.py`) that talks to `AppController`.
  - `pymsc/lib1553`: message formats, ctypes `Structure` payloads and `structures.py` (UDP/1553 wrappers & `CommandWord`/`Udp1553Message`).
  - `demo_bus_monitor/`: minimal listener & tools for manual testing and init scripts (see its `README.md`).
  - `tests/`: pytest-based tests (see `pytest.ini`).

- **Key runtime commands** (PowerShell examples):
  - Run GUI app: `python -m pymsc`
  - Run demo monitor: `python -m demo_bus_monitor.monitor --ip 0.0.0.0 --port 61553`
  - Send initialization sequence (in separate terminal): `python demo_bus_monitor/tools/init_connection.py --ip 127.0.0.1 --listen`
  - Run tests: activate venv then `pytest` or `python -m pytest`.

- **Message + parsing conventions**:
  - Payload types are `ctypes.Structure` classes in `pymsc/lib1553/messages/*`. Sizes are critical: decoding compares `ctypes.sizeof(payload_cls)` against observed payload length.
  - `AppController._init_messages()` registers messages via `BaseMessage(label, payload_cls, sub_addr=sa, frequency=freq, is_transmit=is_tx)` and populates `AppController.rx_map[sa] = payload_class` — add new message types here.
  - Parser expects little-endian fields (see `struct.unpack_from('<H', ...)`). Markers used: `Marker.CTRL_BEGIN`, `Marker.CTRL_END`, `Marker.END_1553` from `pymsc/lib1553/constants.py`.
  - There are two wrapper formats: a short observed 12-byte wrapper and a full `Udp1553Message`. The receiver validates an inverted-CW (bitwise NOT) present after payload — preserve this check when modifying parser logic.
  - Important quirk: WC (word count) handling uses WC=0 => 0 words (not 32). Keep this behavior for compatibility with captured traces.

- **Runtime behaviour to preserve**:
  - Periodic transmissions are NOT automatically sent on registration. `initialize_radar()` configures messages; `AppController.go()` flips `_sending_enabled` to start the scheduler sending frames. Tests and tools rely on this two-step flow.
  - Scheduler uses a software millisecond counter with a 20ms tick (`time.sleep(0.02)`) and checks `mytime % period_ms == 0` to decide sends — keep this when adding periodic messages to match legacy timing.
  - The receiver is performance-sensitive: avoid synchronous file I/O or prints inside the hot loop. Use `monitor_queue` and `MonitorBuffer` (see `pymsc.core.monitor`) to pass decoded entries to GUI or tests.

- **Where to add/extend payloads**:
  - Add new message payload `ctypes.Structure` in `pymsc/lib1553/messages/` and update `_init_messages()` in `pymsc/core/app_controller.py` to register it (label, payload class, SA).
  - For richer examples, consult `_old/Grifo_E_1553lib/messages` (legacy reference implementations).

- **Logging and artifacts**:
  - `AppController` writes logs to `logs/` (`app_debug.log`, `rx_messages.log`, `tx_messages.log`, `udp_raw.log`). Tests and debugging expect these files to be created; ensure `os.makedirs(logs_dir, exist_ok=True)` when changing logging.

- **Tests & quick checks**:
  - `tests/test_imports.py` ensures module importability — run it after renames/refactors.
  - Prefer running a focused test: `pytest tests/test_monitor_buffer.py -q` to verify monitor buffering without launching GUI.

- **Integration points & network expectations**:
  - UDP ports: `udp_send_port=51553` (sent to radar), `udp_recv_port=61553` (listener). The demo README clarifies which side to bind for BC vs RT.
  - Some tools (`demo_bus_monitor/tools/*`) emulate the radar init sequence; use them for integration tests instead of connecting to real hardware.

- **Common pitfalls to avoid**:
  - Do not add blocking disk I/O / prints inside the receiver or scheduler loops — it causes timing issues.
  - Preserve little-endian parsing and inverted-CW validation; many tests and legacy traces rely on these exact checks.
  - When changing payload sizes or bitfields, update both `ctypes` definitions and places that compute `payload_len` (receiver logic in `app_controller.py`).

- **If you need to change high-risk code** (parsing, scheduler, or message definitions):
  - Add unit tests in `tests/` covering both short-wrapper and full-wrapper parsing paths (there are existing test data and tests in `tests/` and `demo_bus_monitor/tests`).
  - Run `pytest` and a targeted integration run of `demo_bus_monitor` using the `init_connection.py` tool to validate end-to-end behavior.

If anything above is unclear or you'd like more examples (e.g., a template `ctypes.Structure` for a new B-message), tell me which area to expand and I will iterate.
