# Plan: Uncertainty Zone Diamonds for Non-Exact Detections

## Context

In HC's native GE (`Display.c`), when a group is detected but its exact position is unknown (`ShowUncertain` set, `ShowExact` not set), the GE renders a **4-point diamond** (closed polyline) around the group's uncertainty region. The group icon is drawn at the diamond's center (average of the 4 vertices). The MapWindow DLL currently ignores this distinction — uncertain groups are rendered identically to exact groups, placed at `sLon`/`sLat` with no visual indication that the position is approximate.

### How the GE does it (Display.c, `DisplayUncertainGroup`)

```c
// Decision logic (line ~750):
if ((Gptr->side == side) || (Gptr->showing & ShowExact) || (ShowAllFlag))
    DrawGroup(hdc, Gptr, TRUE);       // exact position — draw at sLon/sLat
else if (Gptr->showing & ShowUncertain)
    DisplayUncertainGroup(hdc, Gptr); // uncertain — draw diamond + icon at center
else
    Gptr->op_map_flag = FALSE;        // not showing

// DisplayUncertainGroup draws:
// 1. A closed diamond from UncertaintyRegion->sLon[0..3], sLat[0..3]
//    (4 points: N, E, S, W extremes of possible positions)
// 2. Group icon at center (average of 4 vertices)
// Pen color = SideColors[GetGroupSide(Gptr)]
```

### HC data structures

```pascal
URegion = packed record
  sLon: packed array[0..3] of long;  // slivers
  sLat: packed array[0..3] of long;  // slivers
end;

THCGroup = packed record
  ...
  UncertaintyRegion: PURegion;  // nil when no uncertainty zone
  xShow, yShow: LongInt;       // GE-internal display coords (not used by DLL)
  sLon, sLat: LongInt;         // actual position (slivers)
  ...
  showing: uchar;               // bitmask: SideKnown=$01, ShowExact=$02, ShowUncertain=$04
  ...
end;
```

### Key insight: position source

- **Exact** (`ShowExact` set): icon at `pGroup^.sLon/sLat` (current behavior, correct)
- **Uncertain** (`ShowUncertain` set, `ShowExact` not set): icon at **center of UncertaintyRegion** (average of 4 vertices), plus diamond outline
- **Both set** (`ShowExact or ShowUncertain`): the GE checks `ShowExact` first, so exact takes priority — no diamond

---

## Approach

### New layer: `layUncertainty`

A dedicated `TGlobeLayer` (render-only, `Enabled := False`) for uncertainty diamond polylines. Separate from `layGroups`/`layArrows` so diamonds render behind icons but can be cleared independently.

### New helper unit: `uUncertainty.pas`

Follows the established Create/Update/Remove pattern:

```
CreateUncertaintyZone  — TPolylinePresenter + TGeoDataObject with 5-point chain (4 vertices + close)
UpdateUncertaintyZone  — update chain points and presenter color
RemoveUncertaintyZone  — delete presenter + object, free
```

### New fields on `TGroupMapEntry`

```pascal
UncertPresenterID: Integer;  // -1 if no diamond
UncertObjectID:    Integer;  // -1 if no diamond
```

### Rendering logic

The diamond is a closed `TPolylinePresenter` with 5 points in the chain (vertices 0-3 + repeat of vertex 0 to close). This is the same technique used for range rings. No fill — outline only, matching the GE's `CreatePen`/`LineTo` approach.

Pen color = `SideColor(DispSide)` (respects unknown-side yellow from the just-committed feature).

### Position adjustment for uncertain groups

When a group is uncertain (has `UncertaintyRegion` and `ShowExact` is NOT set), the icon should be positioned at the **center** of the uncertainty region rather than at `sLon`/`sLat`. The center is calculated as the average of the 4 vertices:

```pascal
centerLon := (region^.sLon[0] + region^.sLon[1] + region^.sLon[2] + region^.sLon[3]) div 4;
centerLat := (region^.sLat[0] + region^.sLat[1] + region^.sLat[2] + region^.sLat[3]) div 4;
```

This matches the GE's `sumXmap >> 2` / `sumYmap >> 2` logic.

---

## Files Modified

### 1. `uGroupTypes.pas`
- Add `UncertPresenterID: Integer` and `UncertObjectID: Integer` to `TGroupMapEntry`

### 2. `uUncertainty.pas` (new unit)

Procedural helper following the Create/Update/Remove pattern:

```pascal
procedure CreateUncertaintyZone(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  aColor: TColor; var ioNextID: Integer;
  out presID, objID: Integer);

procedure UpdateUncertaintyZone(aLayer: TGlobeLayer;
  objID, presID: Integer;
  const pRegion: PURegion; aColor: TColor; visible: Boolean);

procedure RemoveUncertaintyZone(aLayer: TGlobeLayer; presID, objID: Integer);

function UncertaintyCenter(const pRegion: PURegion): TPointLL;
```

**`CreateUncertaintyZone`**: Creates a `TPolylinePresenter` (outline, no fill) and empty `TGeoDataObject` on `layUncertainty`. Chain starts empty (0 points). Same pattern as `CreateTrackPair` but simpler — single presenter/object pair.

**`UpdateUncertaintyZone`**: If `visible`, converts the 4 `URegion` sliver pairs to `TPointLL` via `HCLon2Deg`/`HCLat2Deg`, writes 5 points into `Chains[0]` (4 vertices + close back to vertex 0). Updates pen color. If not visible, sets `Chains[0].Count := 0`.

**`RemoveUncertaintyZone`**: Standard delete+free pattern.

**`UncertaintyCenter`**: Returns the centroid `TPointLL` from a `PURegion` (average of 4 sliver pairs, converted to globe units).

### 3. `mainform.pas`

**`FormCreate`**: Create `layUncertainty` layer after `layRangeRings`, before `layTracks`. `Enabled := False` (render-only).

**`AddGroup`**:
- Initialize `lEntry^.UncertPresenterID := -1; lEntry^.UncertObjectID := -1`
- Determine if group is uncertain: `lIsUncertain := ((pShowing and ShowUncertain) <> 0) and ((pShowing and ShowExact) = 0) and (pGroup^.UncertaintyRegion <> nil)`
- If uncertain:
  - `CreateUncertaintyZone(...)` with `SideColor(lDispSide)`
  - `UpdateUncertaintyZone(...)` to populate chain from `pGroup^.UncertaintyRegion`
  - Override `lCenterPos` to `UncertaintyCenter(pGroup^.UncertaintyRegion)` — icon/arrow/tracks all anchor to diamond center
- If exact: no diamond, `lCenterPos` stays at `sLon/sLat` (unchanged behavior)

**`UpdateGroup`**:
- Same uncertain check each tick
- If uncertain now:
  - Create diamond if `UncertPresenterID < 0`, then update zone + color
  - Recompute `lNewPos` from `UncertaintyCenter` (so icon follows diamond center)
- If exact now but diamond exists:
  - `RemoveUncertaintyZone(...)`, reset IDs to -1
  - `lNewPos` stays at `sLon/sLat`
- If exact and no diamond: no change (common case)

**`RemoveGroupEntry`**: Add `RemoveUncertaintyZone` call if `UncertPresenterID >= 0`.

**`ClearAllGroups`**: Add `layUncertainty.Clear; layUncertainty.Enabled := False` alongside other layer clears.

### 4. `MapWindow.lpr`
- Add `uUncertainty` to uses clause

### 5. `MapWindow\MapWindow.txt`
- Add history entry

---

## Design Decisions

### Why TPolylinePresenter, not TPolygonPresenter?

The GE draws an unfilled diamond outline (`CreatePen` + `LineTo`). `TPolylinePresenter` gives us a closed polyline with just pen color, no fill. This matches the GE behavior and avoids the complexity of fill/brush transparency. It's also the same presenter type used for range rings and tracks, keeping the codebase consistent.

### Why a separate layer?

Diamonds should render behind group icons but in front of the map. A dedicated layer (`layUncertainty`) between `layRangeRings` and `layTracks` gives correct z-ordering and allows bulk clear on `ScenLoad` without touching other visual concerns.

### Why center the icon on the diamond?

The GE explicitly computes the center of the 4 vertices (`sumXmap >> 2`) and places the group icon there, not at `sLon/sLat`. This is important because for uncertain contacts, `sLon/sLat` is the group's *actual* position which the player shouldn't see — the uncertainty region is what the player's sensors have determined. Centering on the diamond prevents leaking precise position.

### God mode and own-side groups

God mode sets `ShowExact`, so `(pShowing and ShowExact) <> 0` is always true — no diamond is drawn. Same for own-side groups where `HCGroupShowing` returns `ShowExact or SideKnown`. This matches the GE: you only see diamonds for contacts your side has detected imprecisely.

### Null UncertaintyRegion guard

Even if `ShowUncertain` is set, `pGroup^.UncertaintyRegion` could be nil (edge case during initial detection). The code guards against this — if nil, the group is treated as not-uncertain and uses `sLon/sLat` as a fallback.

---

## Edge Cases

| Scenario | `pShowing` | `UncertaintyRegion` | Result |
|---|---|---|---|
| Own-side | `$03` (Exact+SideKnown) | ignored | No diamond, icon at sLon/sLat |
| God mode | `$03` | ignored | No diamond, icon at sLon/sLat |
| Enemy, exact | `$03` or `$07` | ignored | No diamond, icon at sLon/sLat |
| Enemy, uncertain | `$04` or `$05` | non-nil | Diamond drawn, icon at center |
| Enemy, uncertain | `$04` | nil | No diamond, icon at sLon/sLat (fallback) |
| Uncertain -> exact transition | `$04` -> `$03` | | Diamond removed, icon moves to sLon/sLat |
| Exact -> uncertain transition | `$03` -> `$04` | | Diamond created, icon moves to center |
| Uncertain + SideKnown | `$05` | non-nil | Diamond in actual side color |
| Uncertain, side unknown | `$04` | non-nil | Diamond in yellow (UNKNOWNSIDE) |

---

## Verification

1. `lazbuild --build-all MapWindow.lpi` — compiles without errors
2. Launch HC, load scenario, **God mode**: no diamonds visible (all groups exact)
3. Switch to **Player mode**: uncertain enemy groups show diamond outline + icon at diamond center
4. Diamond color matches display side (yellow if unknown, actual color if `SideKnown`)
5. As detection improves (uncertain -> exact), diamond disappears, icon snaps to exact position
6. If contact is lost and re-acquired as uncertain, diamond reappears
7. Own-side groups never show diamonds
8. `ScenLoad` clears all diamonds cleanly
