# Plan: Persist Group Map Objects Between Game Ticks

## Status

**COMPLETE — 2026-03-11.** All steps implemented and committed (revisions 973–974). `SideColor` helper also extracted to eliminate duplicated case blocks (rev 974).

---

## Context

Currently, every `GameTime` tick destroys all group presenters/objects on the globe layer (`ClearGroups`) and rebuilds them from scratch. This causes unnecessary allocation churn, prevents selection persistence on the map, and blocks future features like trails. The fix: use `PHCGroup.UUID` as a stable key to track groups across ticks, only creating/destroying presenters when groups appear or disappear, and updating properties in-place otherwise.

---

## Files to Modify

| File | Changes |
|---|---|
| `mainform.pas` | Add `TGroupMapEntry` record, `FGroupMap` field, `UpdateGroup`, `RemoveGroupEntry`, `ClearAllGroups` methods; modify `AddGroup` to register entries |
| `MapWindow.lpr` | Rewrite `GameTime` body with mark/update/sweep; call `ClearAllGroups` in `ScenLoad` |

## Reference Files (read-only)

- `uHCTypeDefs.pas` — `THCGroup.UUID` field (`packed array[0..36] of AnsiChar`)
- `TGClasses.pas` — `TGlobeObjectSource.Delete(Obj)` and `TGlobePresenterStore.Delete(Pres)` both remove from arrays but do NOT free; caller must free
- `uCICG4Presenters.pas` — `TCICG4PointPresenter` properties (`AngleDeg`, `Image`, `PointPen.PenColor`)

---

## Step 1 — Define `TGroupMapEntry` and helpers in `mainform.pas`

Add a record to track each group's globe objects:

```pascal
type
  PGroupMapEntry = ^TGroupMapEntry;
  TGroupMapEntry = record
    UUID: string;
    IconPresenterID: Integer;
    IconObjectID: Integer;
    ArrowPresenterID: Integer;  // -1 if no arrow
    ArrowObjectID: Integer;     // -1 if no arrow
    Visited: Boolean;           // mark flag for sweep phase
    // Future: trail presenter IDs, uncertainty region IDs, etc.
  end;
```

No cached state — position, course, speed, side, and gtype are updated unconditionally each tick. The expensive operations (bitmap creation, presenter allocation) only happen on add/remove; property updates are cheap.

`HCUUID` is `packed array[0..HCUUID_LEN-1] of AnsiChar` — cast directly to `String` for use as the `FGroupMap` key. No helper needed.

## Step 2 — Add `FGroupMap` to `TfrmMain`

- Field: `FGroupMap: TStringList` (Sorted, keys are UUID strings, `Objects[]` stores `PGroupMapEntry`)
- Init in `FormCreate` with `Sorted := True`
- Free entries + list in `FormDestroy`

## Step 3 — Write `RemoveGroupEntry`

Removes one group's presenters and objects from `layGroups`:

1. If arrow exists (`ArrowPresenterID >= 0`): find arrow object via `ObjectByID`, delete + free it; find arrow presenter via `Presenters.ByID`, delete + free it
2. Find icon object via `ObjectByID`, delete + free it
3. Find icon presenter via `Presenters.ByID`, free its `Image`, delete + free the presenter
4. `Dispose(pEntry)`

## Step 4 — Write `ClearAllGroups`

Bulk reset for scenario/savegame transitions. Calls the existing `ClearGroups(layGroups)` to efficiently wipe the layer, then iterates `FGroupMap`, disposes all entry records, and clears the string list. Used in `ScenLoad` and `FormDestroy`.

## Step 5 — Modify `AddGroup`

After creating presenters/objects (existing code), also:
- Allocate a `TGroupMapEntry` via `New()`
- Populate all fields (IDs from the newly created presenters/objects, cached position/course/kts/side/gtype)
- Add to `FGroupMap` with UUID string as key

## Step 6 — Write `UpdateGroup`

`procedure UpdateGroup(pEntry: PGroupMapEntry; pGroup: PHCGroup; pShowing: uchar);`

Unconditionally update all properties each tick (no change detection — updates are cheap, creation/destruction is expensive):

- **Position:** Update `TGeoDataObject.Centroid` for icon (and arrow if present)
- **Icon:** Regenerate bitmap via `icons.Draw` with current `GetIconIndex(side, gtype)` — handles side/type reclassification
- **Arrow lifecycle:** If `kts > 0` and no arrow exists → create arrow presenter + object. If `kts = 0` and arrow exists → remove arrow presenter + object. If arrow exists → update `AngleDeg` and `PenColor`.
- **Arrow position:** Update arrow data object Centroid to match icon

## Step 7 — Rewrite `GameTime` in `MapWindow.lpr`

```
Phase 1: Mark — set Visited := False on all FGroupMap entries
Phase 2: Walk — iterate PHCGroup linked list:
  - Compute visibility
  - UUID lookup in FGroupMap
  - Found + visible → mark Visited, call UpdateGroup
  - Found + invisible → RemoveGroupEntry, delete from map
  - Not found + visible → AddGroup (creates + registers entry)
  - Not found + invisible → skip
Phase 3: Sweep — iterate FGroupMap in reverse:
  - Any entry with Visited = False → RemoveGroupEntry, delete from map
```

## Step 8 — Update `ScenLoad`

Call `frmMain.ClearAllGroups` at the start of `ScenLoad` to reset all state for the new scenario.

## Step 9 — Keep `ClearGroups`

Keep `ClearGroups` as-is — it's still used by `ClearAllGroups` for efficient bulk layer clearing on scenario/savegame transitions via `ScenLoad`.

---

## Verification

1. Build with `lazbuild --build-all MapWindow.lpi` — zero errors
2. Launch HC, load a scenario — groups appear on map as before
3. Advance time — groups move smoothly, no flicker from full rebuild
4. Watch for groups appearing/disappearing (e.g., aircraft launching) — new groups appear, destroyed groups vanish
5. Toggle Player/God mode — groups correctly appear/disappear based on visibility
6. Load a new scenario mid-session — old groups clear, new ones appear
7. Close HC — no crashes on shutdown (clean teardown of FGroupMap)
