Skip to content

3.0.9 - #15

Merged
heisthecat31 merged 38 commits into
EchoTools:mainfrom
heisthecat31:main
Sep 23, 2026
Merged

heisthecat31 merged 38 commits into
EchoTools:mainfrom
heisthecat31:main

Conversation

@heisthecat31

@heisthecat31 heisthecat31 commented Jul 1, 2026 •

Copy link
Copy Markdown
Collaborator

Changelog for everything are in commits. Ignore secretkeys, will always fail unless .bak gets removed

heisthecat31 and others added 30 commits April 27, 2026 21:07
Changed all Webclient instances in the updater
to HttpClient.

Cleaned up code quality slightly
Make the updater use HttpClient instead of WebClient
Hid FriendTab,
Added Update checker on startup,
Included needed .NET components in build,
Fixed crashing issues at startup.
Added specific numbers option to TTS, "18.67" instead of just "18.6",
Fixed issue causing spark black screen using oculus dash,
Added echo combat dashboard,
Updated overlays (they are still broken a bit),
Fixed issue causing settings to reset for spark,
Updated loading screen tips option,
Fixed 60hz option in replay and updated replay accuracy.
Fixes broken server lookup
Added spectator option to player pings
Fixed TTS
Added so workflow builds correctly
Added FRIENDS_BOT_URL constant for future use.
changed a lot, unsure if it all works, tested it and found 0 issues but you never know.
Full UI overhaul,
Media controller bug fixed not detecting clicks,
Clips Controller bug fixed not detecting clips,
Performance boost by updating event logger,
Updated update function,
Removed useless code and update systems,
Improved theme controller,
Added session logger,
Tracking all time spark usage when echo is open (minutes)
added pvu blue as a colour theme
MainLoop runs on the thread pool via Task.Run.
Thread.Sleep held that worker for the whole
inter-frame delay instead of releasing it
MatchPlayer converted between sample counts and seconds using
StatsIntervalMs, which is the rate the fetch loop targets, not the one it
gets.

Windows wakes on 16 ms ticks, so 60hz is more like
40hz
PingIPList was async void, so an exception had no
Task to surface on and went straight to the
runtime. It pings every address on the subnet,
where failures are the normal case
Spark already banked lifetime playtime locally in
SparkSettings.totalPlaytimeSeconds but never did anything with it beyond
the All-Time figure on the Session card. This contributes it to a shared
pool and shows the community total alongside the personal one.

Clients report their running total rather than a delta, which makes the
call idempotent: it rides the existing 60s CCU heartbeat, whose response
nobody checks, so a dropped reply, a retry or a duplicate has to cost
nothing. An install that was closed for a week just catches up on its
next report.

The reported identifier is SHA-256 of the MAC rather than Logger.DeviceId
itself, which is the raw MAC. The pool needs to tell installs apart to
de-duplicate and rate-limit them; it does not need to know whose they
are. Gated behind the new shareGlobalPlaytime setting, on by default,
with a toggle in Settings > General.

Also splits playtime accrual out of UpdateSessionCard into BankPlaytime
and moves it ahead of the window-visibility gate. Playing with Spark
minimised to the tray is the normal case, and while accrual sat with the
rest of the Session card that time was credited to neither the local nor
the global total.

Requires the matching /playtime endpoints on the Railway backend; until
those deploy the card shows "--" and reports are swallowed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reports a display name alongside the playtime total so the pool can be
ranked, not just summed. Prefers the Discord identity because it is the
one of the two a user cannot casually retype -- client_name is free text
in Settings, so it makes a weak claim to an identity. Reads global_name
ahead of the username handle, matching what the rest of the app shows,
and guards on discordUserData rather than IsLoggedIn alone: the latter
only checks for a token, and the profile fetch that fills it can still
have failed.

An install with neither name still contributes to the community total,
it just gets no row on the board.

The settings tooltip previously promised "no username", which adding a
name makes false, so it now states exactly which name is sent and when.

Version to 3.1.8 across the csproj and the MSIX manifest; the MSI picks
it up on its own via GetAssemblyIdentity against the built assembly.

Requires the matching /playtime/leaderboard endpoint on the backend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Quest Spectator could only find people by scanning the public match API,
which lists neither private matches nor social lobbies — so it could never
follow anyone into one. Friends presence has no such limit: a player's own
Spark knows the session id it is in and publishes it, whatever kind of
session that is.

- Rename the feature to Simple Spectate Mode and present it as "spectate
  anyone": the public API still covers non-friends in public matches, and
  presence extends that to private matches and lobbies for friends.
- Add self-follow, so two PCs on one Discord account can spectate each
  other. Both share a presence row, so the spectating copy suppresses its
  own pushes or it overwrites the session it is reading.
- Read lobby session ids out of the client's r14 log. The /session API
  answers -6 with no id in a lobby, so this is the only way a friend in a
  lobby is joinable. Matches keep the API's id.
- Heartbeat presence every 30s. It was only pushed on connection-state
  change, so anyone in one match past the bot's 120s staleness window went
  silently offline to their friends.
- Register with the bot before the first push. Presence is an UPDATE, and
  registration only happened when the Friends tab was opened — a tab that
  is off by default and whose content WPF never instantiates while hidden,
  so most users were pushing into nowhere.
- Theme the Friends tab: colours now come from the theme via DynamicResource
  and enum-driven triggers rather than hardcoded hex, so it repaints live.
- Friends tab on by default, with a settings migration so existing users
  get it. Changing a default only reaches fresh installs otherwise, since
  Save() writes every key.
- Fix LogRow(LogType.File, ...) calls that passed no file name and so
  logged nothing, including two pre-existing ones in the auto-follow path.

Version 3.1.9.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The "[NSLOBBY] requesting N player session in game session {guid}" line is
only written when the client actively matchmakes a session — not when it is
placed in one or rejoins. Measured across 62 real r14 logs, the room the
client was actually in came from "lobbyid" in the Oculus room data-store
update, and NSLOBBY was:

  - absent entirely in 34 of those logs, so the reader would have reported
    no session at all for the majority of lobbies
  - stale in 1 more, naming a room the client had already left

and there was no log where NSLOBBY knew something lobbyid did not.

So lobbyid is now the sole authority: most recent wins, and the all-zero
guid the client writes on leaving clears it. Two things this has to avoid,
both confirmed against the logs and covered by tests:

  - "lobby_id" (underscore) in ovr_RichPresence_Set lines is often the
    all-zero guid *while* in a room, so the key match is strict.
  - the session teardown markers arrive *after* the next room's lobbyid
    during a lobby-to-match move, so honouring them would clear a room we
    had just correctly picked up. They are no longer consulted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In a social lobby the game API answers error -6 with no session id, so both
join-link boxes sat on their placeholder and a lobby could not be shared or
copied. EchoLogSessionReader already knows the room from the client's own
log, so use that whenever the connection state is InLobby and the frame's
session id otherwise.

Both boxes now go through one writer rather than being set from lastFrame
in three places — the previous sites were all inside a `lastFrame != null`
check, which is never true in a lobby, which is precisely when the log is
the only source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The join link reads CurrentSessionId from the UI tick, and the getter used
to do the file work inline — rate-limited to once a second, but still disk
I/O on the UI thread. A watcher refreshes it off-thread instead and the
getter just returns what it last found.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The log line was written before the request went out and the response was
never checked, so a rejected push — expired Discord token, bot down — read
exactly like a successful one. That is worse than no line at all when the
question being asked is "why can't my friend see me".

Successes still only log when what we advertise changes, since the
heartbeat repeats every 30s. Failures always log, and clear the dedupe so
the next success is visible rather than being swallowed as unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Re-added friends tab,
Made building easier,
Fixed session time exploits (nebula seriously),
Updated echo vr speaker system.
Added replay analyser as a tab, merged from separate repo,
Fixed combat dashboard kill feed not updating,
Added name converting for combat gun names and stuff,
Added more TTS speed options,
Added private match only option to Ping Spike TTS,
Added option to fix broken .butter files to convert to echoreplay files,
Updated how butter files record to hopefully avoid future corruption.
Updated installation link and .NET version in README.
Added sections for Idle Dash and Combat Dash with images.
 Fix three async/timing bugs in the fetch loop and quest scanner
Added a new image for Arena Dash in the README.
@heisthecat31
heisthecat31 merged commit ab750d9 into EchoTools:main Sep 23, 2026
1 check passed
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.

3 participants