Skip to content

feat(intel): per-character system tracking from Local logs#65

Merged
AreteDriver merged 2 commits into
feat/preview-focus-modefrom
feat/per-character-system-tracking
Apr 26, 2026
Merged

feat(intel): per-character system tracking from Local logs#65
AreteDriver merged 2 commits into
feat/preview-focus-modefrom
feat/per-character-system-tracking

Conversation

@AreteDriver
Copy link
Copy Markdown
Owner

Stacked on #64#63#62. Merge order: #62#63#64 → this. Each PR auto-retargets to `main` as its base merges.

Summary

EVE writes a separate `Local_*.txt` chat log per character session. The header has a `Listener:` line naming the character; the body has `Channel changed to Local : ` lines whenever that character jumps. We parse both into a per-character current-system map.

What's new

  • `intel/character_location.py` — `CharacterLocationTracker(QObject)`:
    • Polls `Local_*.txt` files (UTF-16-LE) in the EVE chat log directory
    • Parses `Listener:` header once per file (4KB scan, regex)
    • Tails for `Channel changed to Local : X` and emits `character_system_changed(char_name, system)` only on real changes
    • File rotation/truncation handling, garbage-line rejection, idempotent start/stop
  • `StatusDock.set_character_system` — updates every chip for a character (multibox-aware: same character in multiple windows all sync together)
  • `WindowManager._character_systems` — persistent per-character map that survives window add/remove cycles
  • `MainWindowV21._init_location_tracker` + `_on_character_system_changed` slot — forwards updates to dock + manager
  • `MainTab._sync_status_dock` seeds chip systems from cached state so newly-imported windows populate without waiting

Why this matters

This is the architectural unlock for chip-level / frame-level threat differentiation. Previously the parser tracked one global `current_system` (`intel/parser.py:326`) — every chip and frame shared it. Now each character has independent location state, so a future PR can tint only the frames whose character is in (or near) the affected system.

Phase A — explicit scope

This PR ships the tracker + UI sync of system labels only. Smart per-character threat fan-out (filter `apply_threat_state` by character system) is deliberately a follow-up. The storage map exists; the fan-out semantics decision is parked until per-char tracking is observed in production.

Test plan

  • 33 new tests in `tests/test_character_location.py` (header parsing, channel-changed line parsing, multi-character, file truncation, lifecycle, parametrized garbage-line rejection, dock + manager setters, MainWindowV21 wiring, MainTab seeding)
  • 1 existing test patched (`test_init_creates_core_modules` now also patches `_init_location_tracker`)
  • Full suite: 2289 passed, 5 skipped, 0 failures
  • `ruff check` + `ruff format --check` clean
  • Manual smoke: run with `intel.track_character_locations=true` while EVE is running, jump systems on multiple characters, verify each chip's system label updates independently

Settings

  • `intel.track_character_locations` — default `true`. Tracker idles if false or if no EVE log directory is found.

Follow-ups (future PRs)

  • Smart fan-out: `apply_threat_state(level, system)` → only tint frames whose character is in `system`, with a fallback for unknown locations
  • Jumps-from: integrate `JumpCalculator` per-character so danger pulses on chips in adjacent systems too
  • Per-character accent border: frame inherits chip's accent color when threat is clear

🤖 Generated with Claude Code

EVE writes a separate Local channel chat log per character session. Each
file has a Listener: header that names the character, and "Channel
changed to Local : <System>" lines whenever that character jumps. We
parse both to maintain a per-character current-system map.

Adds intel/character_location.py — CharacterLocationTracker(QObject):
  - Polls Local_*.txt files (UTF-16-LE) in the EVE chat log directory
  - Reads the Listener: header once per file
  - Tails for "Channel changed to Local : X" lines
  - Emits character_system_changed = Signal(str, str) on real changes
  - get_system / get_all_locations for cached lookup
  - Idempotent start/stop, file rotation handling, garbage-line rejection

Wires into the existing UI:
  - StatusDock.set_character_system(char_name, system) updates every
    chip for that character (multibox-aware: same character in multiple
    windows all sync)
  - WindowManager._character_systems map persists across window add/remove
    cycles, exposed via set_character_system / get_character_system
  - MainWindowV21 constructs the tracker, connects character_system_changed
    to forward to dock + manager, and stops it on close
  - MainTab._sync_status_dock seeds chip systems from the cached map so
    newly-imported windows populate without waiting for the next change

Behavior is gated on intel.track_character_locations (default true). The
tracker idles if it can't find an EVE log directory.

This PR is intentionally Phase A: the tracker drives chip system labels
only. Smart per-character threat fan-out (only tinting frames whose
character is in the affected system) is a follow-up — the storage map
exists, the fan-out semantics decision is deferred.

33 new tests in tests/test_character_location.py + 1 existing test
patched (init test now also patches _init_location_tracker). Suite:
2289 passed, 5 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a CharacterLocationTracker to monitor EVE Online chat logs for character system changes and update the UI. Feedback points out a performance risk in scanning the entire log directory on the GUI thread and a potential bug where advancing the file cursor during partial reads could cause missed events.

if self._log_directory is None or not self._log_directory.exists():
return
try:
files = list(self._log_directory.glob(_LOCAL_FILE_GLOB))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Scanning the entire chat log directory every 2 seconds is a significant performance risk. EVE Online players often accumulate thousands of log files over time. Since glob("Local_*.txt") returns all historical logs and this method runs on the GUI thread, it can cause the application to hang or stutter as it iterates and stats every file. It is recommended to limit the search to files modified recently (e.g., today and yesterday) using a more specific glob pattern based on the date strings in the filenames.

Suggested change
files = list(self._log_directory.glob(_LOCAL_FILE_GLOB))
import time
today = time.strftime("%Y%m%d", time.localtime())
yesterday = time.strftime("%Y%m%d", time.localtime(time.time() - 86400))
try:
files = list(self._log_directory.glob(f"Local_{today}_*.txt"))
files.extend(self._log_directory.glob(f"Local_{yesterday}_*.txt"))

Comment on lines +237 to +238
state.position = fb.tell()
except OSError as e:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Updating state.position to fb.tell() immediately after reading to EOF can lead to missed events if a log line is partially written at the moment of polling. The partial line will be read, fail to match the regex (because it is incomplete), and then be skipped in subsequent polls because the cursor has moved past it. To ensure robustness, the tracker should only advance the position to the end of the last complete line (ending in a newline) found in the buffer, or buffer the remainder for the next poll.

@AreteDriver AreteDriver merged commit b3457c6 into feat/preview-focus-mode Apr 26, 2026
1 check passed
@AreteDriver AreteDriver deleted the feat/per-character-system-tracking branch April 26, 2026 09:40
AreteDriver added a commit that referenced this pull request Apr 26, 2026
Bumps version to 3.2.0 and documents the 10-PR intel-aware UI arc that
landed via #74. Also fixes the pre-existing version drift between
pyproject.toml (3.1.2) and __init__.py (3.0.4) by syncing both to 3.2.0.

CHANGELOG entry covers:
- Intel-aware preview borders (PR #62)
- Character status dock (PR #63)
- Preview focus mode (PR #64)
- Per-character system tracking from EVE Local logs (PR #65)
- Smart per-character threat fan-out (PR #66)
- Jumps-from fan-out with adjacency falloff (PR #67)
- "+Nj" distance badges on chips + frames (PR #68 + PR #71)
- Per-character accent border (PR #70)
- Toggleable replay strip with frame scrubbing (PR #72)

Plus the supporting infrastructure (intel/threat_filter, accent palette
promotion, set_threat_state kwarg additions) and the new settings keys
that gate the features.

Co-authored-by: AreteDriver <AreteDriver@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant