Skip to content

feat: join diagnostics -- KitsuneJoinDiag mod + KC Join Attempts panel - #96

Merged
AdaInTheLab merged 3 commits into
mainfrom
feat/join-diagnostics
May 28, 2026
Merged

AdaInTheLab merged 3 commits into
mainfrom
feat/join-diagnostics

Conversation

@AdaInTheLab

Copy link
Copy Markdown
Collaborator

Summary

Two complementary additions for diagnosing failed 7DTD joins, finished during the 2026-05-28 session:

  • src/KitsuneJoinDiag/ (new mod) — client-side Harmony postfix on NetworkClientLiteNetLib.OnDisconnectedFromServer. Vanilla 7DTD catches the real DisconnectReason and then throws it away in favor of a generic "Could not retrieve server information" dialog; this mod logs the actual reason + a short player-actionable hint at ERR level in Player.log. Verified live on 2026-05-28 — caught a real ConnectionFailed on the goodtimes playit tunnel and surfaced the block exactly as designed.
  • KC Join Attempts panel — server-side companion. In-memory ring buffer (500 events, single lock) of LiteNetLib auth-wrapper events captured via five Harmony patches on NetworkServerLiteNetLib+LiteNetLibAuthWrapperServer. Admin-only REST under /api/join-attempts (GET with limit + since, POST /clear, POST /verbose). Vue admin view auto-refreshes and groups by peer. i18n added across all 8 locales (de/es/fr properly translated; ja/ko/zh-CN/zh-TW use English placeholders).

Together they give both sides of a failed-join investigation: the player sees the real DisconnectReason in their Player.log, the admin sees the event hit the server's ring buffer (or sees that it didn't, which is also diagnostic).

Companion Kitsunebi cards on packrelay-raunk:

  • #216 — KitsuneJoinDiag v0.2 polish: peer port reports local source port not target; timeSinceLastPkt is NetManager-global and meaningless on fresh failures
  • #227 — make Connect actually connect: client-side Harmony patch for in-game auto-join (lands separately)

Test plan

  • dotnet build src/KitsuneJoinDiag/KitsuneJoinDiag.csproj -c Release produces a clean KitsuneJoinDiag.dll
  • dotnet build src/KitsuneCommand/KitsuneCommand.csproj still succeeds (LiteNetLib reference added)
  • cd frontend && npm run build succeeds (new view + locale keys + router)
  • Drop the JoinDiag DLL into a 7DTD client's Mods folder; trigger a known-failing connect; confirm [KitsuneJoinDiag] CONNECTION FAILED block lands in Player.log with the expected fields
  • Start KC against a 7DTD server, attempt a join (success or fail), verify events appear in /api/join-attempts and in the new admin-only Vue panel
  • Confirm the Join Attempts nav entry is NOT visible to non-admin users
  • Run the script at src/KitsuneJoinDiag/tools/test-joindiag.ps1 -Watch while triggering a failed connect — confirm the script captures the block

🤖 Generated with Claude Code

AdaInTheLab and others added 3 commits May 27, 2026 13:11
The data dir was anchored to GameIO.GetSaveGameDir(), which returns the
*current world's* save folder. Every new world (or any 7DTD boot that
landed on a different save dir for any reason) produced an empty
KitsuneCommand DB and re-ran AuthService.EnsureAdminExists, silently
rotating the admin password and writing a fresh FIRST_RUN_PASSWORD.txt.

Observed on a live server: four "FIRST RUN" blocks in two days, each
generating a new random password and invalidating the operator's stored
panel creds with no obvious cause. The only fingerprint was the
recurring banner in the nssm log.

Fix: anchor KC's persistent data (SQLite DB, appsettings.json override,
FIRST_RUN_PASSWORD.txt, RESET_PASSWORD.txt) to the 7DTD user-data root
(parent of Saves/) instead of inside any specific save. Survives world
regen, save deletion, and PackRelay mod re-installs.

- ConfigManager.ResolveWorldAgnosticDataDir() — new public static, the
  single source of truth for the data dir path. AuthService and
  WebServerHost both call it instead of duplicating the path walk.
- ConfigManager.TryMigrateLegacyDataDir() — idempotent best-effort copy
  from the legacy per-world location so existing operators don't lose
  their DB on upgrade.
- Two reassurance log lines after the FIRST RUN banner so the operator
  knows subsequent restarts will not rotate the password.
- Bumped to 2.7.4 with a CHANGELOG entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ib DisconnectReason

Vanilla 7DTD catches LiteNetLib's OnPeerDisconnected with the real
DisconnectReason (PeerNotFound, Timeout, ConnectionFailed,
InvalidProtocol, etc.) and then throws it away, showing the player a
generic "Could not retrieve server information" dialog. With that
string alone, neither the player nor an admin can tell whether the
failure is NAT, rate-limit, version mismatch, firewall, or anything
else.

KitsuneJoinDiag is a tiny standalone mod that Harmony-postfixes
NetworkClientLiteNetLib.OnDisconnectedFromServer and logs the real
DisconnectInfo at ERR level in Player.log, plus a short hint mapping
each reason to player-actionable advice. Harmless on dedicated servers
(the client-side code path doesn't fire there), so safe to ship in
either client-only or whole-pack mod distributions.

v0.1 has two known cosmetic issues filed as Kitsunebi card #216:
  - peer:Port logs LiteNetLib's local source port, not the typed
    target port
  - timeSinceLastPkt is a NetManager-global counter, meaningless on
    fresh failures

Also includes tools/test-joindiag.ps1: a PowerShell harness that
extracts the diag block from a ModLauncher profile's output_log.txt
(one-shot or -Watch mode), so you can iterate on the format without
fishing the block out of a 2MB log every time.

LiteNetLib.dll added to refs/ so the mod's csproj can resolve
DisconnectReason / DisconnectInfo at compile time. The DLL is
referenced as Private=false because the game ships its own copy; we
only need the type metadata.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…apper events

Server-side companion to KitsuneJoinDiag. Where the client-side mod
surfaces a player's own DisconnectReason to their Player.log, this
panel gives the admin a view of all join activity hitting the server,
whether or not the connecting client ran any mods.

Backend
-------
- Diagnostics/JoinAttemptEvent.cs: data record for a single observed
  event (timestamp, peer endpoint, event type, result, deliveryMethod,
  bytes, channel, extra data bytes, auth state count).
- Diagnostics/JoinAttemptRing.cs: lock-protected ring buffer, 500
  events, with Record() / Snapshot(limit, sinceUtc) / Clear() /
  TotalRecorded. Single lock is fine at the expected event rate.
- GameIntegration/Harmony/AuthWrapperServerDiagnostics.cs: five
  Harmony patches on NetworkServerLiteNetLib+LiteNetLibAuthWrapperServer
  capturing ConnectionRequestCheck, OnNetworkReceiveEvent,
  OnPeerConnectedEvent, OnPeerDisconnectedEvent, and Update. Each feeds
  JoinAttemptRing.Record(). Verbose console-log gating via a static
  Enabled flag so we can hot-toggle the noisy console output without
  losing the ring data (it's always recorded).
- Web/Controllers/JoinAttemptsController.cs: admin-only REST under
  /api/join-attempts: GET (with limit + since query), POST /clear,
  POST /verbose for the Enabled flag.
- KitsuneCommand.csproj: reference LiteNetLib (Private=false; the
  game ships its own copy, we only need the type metadata) so the
  diagnostics patches can name DisconnectReason / DeliveryMethod /
  ConnectionRequest directly without reflection.

Frontend
--------
- views/JoinAttemptsView.vue: PrimeVue DataTable view with
  auto-refresh, peer grouping, and per-event detail. Admin-only
  (server enforces, client checks roles before showing the nav
  entry).
- api/joinAttempts.ts: typed thin wrapper over the REST endpoints.
- router/index.ts: route registration.
- components/layout/AppLayout.vue: nav entry visible to admins.
- i18n: keys added across en/de/es/fr/ja/ko/zh-CN/zh-TW. Non-English
  Asian locales (ja, ko, zh-CN, zh-TW) use English placeholders for
  now; de/es/fr have proper translations.

Pairs with KitsuneJoinDiag v0.1 (sibling mod, separate commit) to
give both sides of a failed-join investigation: the player sees the
real DisconnectReason in their Player.log, the admin sees the event
hit the server's ring buffer (or sees that it didn't, which is also
diagnostic).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AdaInTheLab
AdaInTheLab merged commit 04099ad into main May 28, 2026
2 checks passed
@codecov

codecov Bot commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 108 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
frontend/src/views/JoinAttemptsView.vue 0.00% 97 Missing ⚠️
frontend/src/api/joinAttempts.ts 0.00% 10 Missing ⚠️
frontend/src/router/index.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

AdaInTheLab added a commit that referenced this pull request May 29, 2026
…og test types (#99)

The v2.8.0 tag build failed two ways:

- x64/libSkiaSharp.dll and .so are gitignored (only sqlite3.dll was force-added),
  but #96 added <None Include="x64\libSkiaSharp.*"> to the csproj. CI checks out
  without them -> MSB3030 "could not copy ... not found". Force-add both (like
  sqlite3.dll) so the release zip bundles the natives the mod loads from x64/.
- PlayerEditDialog.test.ts: the `player` mock had 5 of PlayerInfo's 20 fields, and
  mockGetMetadata was inferred to return `undefined` so it rejected the seeded
  {playerId, vipTier}. Completed the PlayerInfo mock, typed the metadata mock as
  PlayerMetadata | undefined, and completed the seeded object. `vue-tsc --noEmit`
  now passes locally.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@AdaInTheLab
AdaInTheLab deleted the feat/join-diagnostics branch June 4, 2026 00:21
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