feat(intel): per-character system tracking from Local logs#65
Conversation
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>
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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.
| 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")) |
| state.position = fb.tell() | ||
| except OSError as e: |
There was a problem hiding this comment.
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.
…at/per-character-system-tracking
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>
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
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
Settings
Follow-ups (future PRs)
🤖 Generated with Claude Code