---
title: Known Bugs
description: Documented bugs in the upstream code and firmware, with analysis and fixes.
sidebar:
  order: 4
---

import { Aside } from '@astrojs/starlight/components';

This page documents bugs we've found and fixed in the upstream code, as well as firmware hazards that can't be fixed in software but need to be understood.

## Leap-frog elevation bug (upstream)

**Location:** `Trav-ler-Rotor-For-HAL-2.05/travler_rotor.py`, lines 98-105

**Severity:** Tracking accuracy degraded on the elevation axis

**Affected repos:**
- `saveitforparts/Trav-ler-Rotor-For-HAL-2.05` -- `travler_rotor.py` lines 98-105
- `saveitforparts/Travler-Pro-Rotor` -- same bug copy-pasted into `travler_pro_rotor.py`

### The bug

The leap-frog algorithm has two sections: azimuth compensation and elevation compensation. Both were written from the same template. In the elevation section, a copy-paste error left `target_az` as the variable being modified instead of changing it to `target_el`.

**Original code (lines 90-105):**

```python
# Azimuth compensation (correct)
if target_az - current_az > 2:
    target_az+=1
elif target_az - current_az < -2:
    target_az-=1
elif target_az - current_az > 1:
    target_az+=0.5
elif target_az - current_az < -1:
    target_az-=0.5

# Elevation compensation (BUG: modifies target_az instead of target_el)
if target_el - current_el > 2:
    target_az+=1              # <-- should be target_el
elif target_el - current_el < -2:
    target_az-=1              # <-- should be target_el
elif target_el - current_el > 1:
    target_az+=0.5            # <-- should be target_el
elif target_el - current_el < -1:
    target_az-=0.5            # <-- should be target_el
```

### Impact

Two things go wrong simultaneously:

1. **Elevation never gets leap-frog compensation.** The elevation delta is computed correctly (`target_el - current_el`), but the adjustment is applied to the wrong variable. During fast satellite passes with significant elevation change, the dish lags behind on the EL axis.

2. **Azimuth gets double compensation.** The azimuth correction from its own section is applied first, then the elevation section adds a second correction to the same variable. If both axes have large deltas (common during a pass), azimuth overshoots its target.

For a satellite pass where both AZ and EL are changing by more than 2 degrees per update:
- AZ gets +2.0 degrees of correction (1.0 from its own section + 1.0 from the elevation section)
- EL gets +0.0 degrees of correction

### The fix

In `birdcage/leapfrog.py`, the elevation section correctly modifies `target_el`:

```python
def apply_leapfrog(
    target_az: float,
    target_el: float,
    current_az: float,
    current_el: float,
) -> tuple[float, float]:
    # Azimuth compensation
    az_delta = target_az - current_az
    if abs(az_delta) > 2:
        target_az += 1.0 if az_delta > 0 else -1.0
    elif abs(az_delta) > 1:
        target_az += 0.5 if az_delta > 0 else -0.5

    # Elevation compensation (fixed: modifies target_el, not target_az)
    el_delta = target_el - current_el
    if abs(el_delta) > 2:
        target_el += 1.0 if el_delta > 0 else -1.0
    elif abs(el_delta) > 1:
        target_el += 0.5 if el_delta > 0 else -0.5

    return target_az, target_el
```

The fix also restructures the conditionals to use `abs()` and ternary expressions, making the symmetry between the two axes explicit and harder to get wrong in future edits.

## Prompt termination bug (console-probe)

**Location:** `console_probe/serial_io.py`, `_is_prompt_terminated()`

**Severity:** False prompt detection causes truncated responses

### The bug

The original prompt detection logic checked whether the response ended with `>`. This worked for the firmware prompt (`TRK>`, `MOT>`, etc.) but also matched the `>` character inside parameter syntax in help text:

```
help [<command>]
```

The `>` in `<command>` would trigger prompt detection, cutting off the rest of the help output.

### The fix

The `_is_prompt_terminated()` function now distinguishes between prompts and parameter syntax using two strategies:

1. **Known prompt matching** -- when the profile has a list of known prompts (`TRK>`, `MOT>`, `DVB>`, etc.), it checks for exact suffix matches. This is the fast path and avoids false positives entirely.

2. **Bracket filtering** -- for pattern-based detection (when known prompts aren't populated yet), the function rejects any line containing `[` brackets before accepting a `>` match. Parameter syntax like `[<command>]` always appears inside brackets.

```python
def _is_prompt_terminated(text: str, profile: DeviceProfile) -> bool:
    last_line = stripped.split("\n")[-1]

    if profile.prompts:
        # Check known prompts first (fast path)
        for p in profile.prompts:
            if last_stripped.endswith(p):
                return True
        # Accept PROMPT_RE match only if no brackets on that line
        if "[" not in last_line:
            m = PROMPT_RE.search(last_line)
            if m:
                return True
        return False

    # No known prompts yet -- fallback to bare > check
    return stripped.endswith(">")
```

During initial discovery (before any prompts are known), the fallback to `stripped.endswith(">")` is intentionally permissive -- it may occasionally truncate, but it gets the first prompt detected so the more precise logic can take over.

## ADC scan deadlock (firmware hazard)

<Aside type="danger" title="This can brick your session">
The ADC `scan` command without arguments on an uncalibrated azimuth axis will deadlock the firmware shell. **No serial input can recover it** -- not CR, Ctrl+C, ESC, `q`, or `reboot`. The only recovery is a hardware power cycle.
</Aside>

### What happens

The ADC submenu's `scan` command performs an azimuth sweep with RSSI readings. When called without explicit position arguments, it reads the current AZ target from the motor controller.

If the AZ motor has not been homed (which happens when NVS 20 disables the tracker, skipping the boot homing sequence), the position register contains the sentinel value **2147483647** (INT_MAX, or 0x7FFFFFFF).

The firmware interprets this as a real target position and commands the motor to move there. The motor task blocks on this impossible move, and because the firmware shell is single-threaded -- UART input parsing only happens between command completions -- the shell becomes permanently unresponsive.

### Why serial input can't help

The K60's UART4 receive buffer fills up with the bytes you send (CR, `q`, etc.), but the main loop never reads them because it's stuck inside the motor move handler. There is no interrupt-based command abort mechanism in this firmware. The motor task runs to completion (or forever, in this case) before control returns to the shell parser.

### Mitigation

- Always home both axes before using ADC `scan`: run `mot` -> `h 0` (AZ) and `h 1` (EL) first
- The `birdcage` software never calls ADC `scan` directly
- The `console-probe` tool's timeout-based reads will eventually time out, but the firmware shell itself remains dead

### Other commands affected

Any command that internally reads motor position and initiates a move could theoretically hit this on an unhomed axis. The `azscanwxp` command in the MOT submenu is similarly dangerous without homing. However, simple position queries (`a` in MOT) safely return the INT_MAX value without attempting a move.

## Root menu `q` command (firmware design, not a bug)

The `q` command at the `TRK>` root prompt terminates the firmware shell task. This is by design -- it's a clean shutdown of the command interpreter. But the consequence is identical to the scan deadlock: the console becomes unresponsive and requires a power cycle.

Inside submenus, `q` safely exits to the parent menu. The hazard is only at the root level.

The `birdcage` code's `reset_to_root()` method sends `q` to exit submenus, which is safe. But if called when already at root, it would kill the shell. The CarryoutG2Protocol avoids this by using `_send("q")` which reads until the prompt -- if the shell dies, `_send` raises a `TimeoutError` instead of silently losing the connection.

## `command` false positive in help parsing

During automated probing, the word `command` appeared as a discovered command in the root menu. This is a false positive extracted from the help text:

```
help [<command>]
```

The help parser saw `<command>` as a valid angle-bracket command name. The fix in `console_probe/discovery.py` maintains a set of known parameter placeholders:

```python
_PARAM_PLACEHOLDERS: set[str] = {
    "command", "commands", "parameter", "parameters",
    "value", "values", "index", "name", "arg", "args",
    ...
}
```

Any word matching this set is rejected during help parsing, preventing it from appearing as a discovered command.
