Commit Graph
195 Commits
Author SHA1 Message Date
neckfire 9cb15ad9e2 fix(netcode): Incoming en sens inverse d'Outgoing (couche chiffrement externe retirée en premier)
Build Game Server / build (push) Successful in 22s
Avec >1 composant actif (stateless + AES), Incoming et Outgoing doivent être en
sens opposés. Les deux étaient en avant -> le stateless tentait de parser des octets
chiffrés. Incoming itère désormais en inverse.
2026-07-16 23:53:52 +02:00
neckfire 5c81fba79c feat(netcode): chiffrement AES-256-GCM keyé par la PSK (piste retenue)
Build Game Server / build (push) Successful in 21s
Le client n'entame pas de handshake DTLS et ferme si nos paquets post-ack sont en
clair. AesGcmHandlerComponent (System.Security.Cryptography.AesGcm, [IV12][ct][tag16]),
activé après l'EncryptionAck : ack en clair -> activation -> Challenge chiffré.
2026-07-16 23:47:00 +02:00
neckfire a3dc1e8543 docs(netcode): findings test live — client ferme après EncryptionAck (attend voie serveur chiffrée) + crash ConditionalCleanUp
Build Game Server / build (push) Successful in 15s
2026-07-16 23:38:10 +02:00
neckfire 7e34c61b2a test(dtls): EncryptionAck sans activer DTLS — le client continue le login EN CLAIR après l'ack (observé live)
Build Game Server / build (push) Successful in 21s
2026-07-16 23:33:33 +02:00
neckfire e397541418 debug(dtls): log en tête d'Incoming (état de la garde active/transport/failed)
Build Game Server / build (push) Successful in 22s
2026-07-16 23:28:37 +02:00
neckfire 3e47b0680c debug(dtls): logs Incoming/Outgoing (voir ce que reçoit le serveur après EncryptionAck)
Build Game Server / build (push) Successful in 21s
2026-07-16 23:24:34 +02:00
neckfire a21ac4d0e4 docs(netcode): mur DTLS franchi côté serveur + boucle d'itération live
Build Game Server / build (push) Successful in 14s
2026-07-16 23:07:43 +02:00
neckfire 36122578a3 feat(netcode): serveur DTLS-PSK (côté serveur) — franchit le mur du chiffrement
Build Game Server / build (push) Successful in 20s
Implémente le pendant serveur du DTLSHandlerComponent d'UE (la pile client est
[DTLS, Stateless]) : le client exige un DTLS en mode PSK dont l'identité = user_id.

- DtlsPskStore : table user_id -> PSK 32o, chargée depuis l'env (PROSPECT_DTLS_PSKS,
  rendu Vault) — aucune clé en dur ni commitée.
- DtlsPacketTransport : pont entre l'API bloquante DTLS de BouncyCastle et le modèle
  paquet-par-paquet du PacketHandler (handshake sur thread dédié).
- ProspectPskTlsServer : serveur DTLS-PSK BouncyCastle (DTLS 1.2, identité=user_id).
- DTLSHandlerComponent : composant du pipeline (Incoming déchiffre / Outgoing chiffre),
  activé au NMT_Hello portant un EncryptionToken.
- UWorld.NotifyControlMessage : sur NMT_Hello chiffré, si la PSK de l'identité est
  connue -> NMT_EncryptionAck + BeginHandshake ; sinon warning (comme avant).

Compile (0 erreur, BouncyCastle 2.4). ⚠️ Le framing exact DTLS-sur-PacketHandler et
le routage des records de handshake demandent une ITÉRATION EN LIVE contre le vrai
client (invalidable hors client).
2026-07-16 23:06:14 +02:00
neckfireandClaude Opus 4.8 f9c44a89b3 game-server: catch-all request capture for squad-invite RE
Build Game Server / build (push) Successful in 25s
Log every unrouted POST at Warning [CAPTURE UNROUTED] (reveals native PlayFab
endpoints the emulator doesn't implement, e.g. /Group/CreateGroup, /Lobby/*) and
every social/squad/invite/friend/matchmaking call (path or body) at Information
[CAPTURE SOCIAL]. Lets us pin the exact invite mechanism when reproduced in game.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:35:37 +02:00
neckfire 4ec54f894b docs(game-server): study PlayFab Groups/Party emulation for squad invites
Build Game Server / build (push) Successful in 18s
2026-07-16 08:28:58 +00:00
neckfire f7048ff173 Merge pull request 'chore(game-server): merge main (emulator fixes: contracts, shop, fortuna, friends, admin, presence)' (#16) from main into game-server
Build Game Server / build (push) Successful in 26s
2026-07-16 08:25:21 +00:00
neckfire 91bdde3797 Merge pull request 'friends: richer GetFriendList payload + logging' (#15) from preprod into main
Build & Deploy / build (push) Successful in 38s
2026-07-16 07:06:20 +00:00
neckfireandClaude Opus 4.8 d7821ebf1f friends: richer GetFriendList payload (field aliases) + log returned JSON
Build & Deploy / build (push) Successful in 53s
The friend tile rendered empty/offline because the UE client couldn't read our
best-guess response shape. Expose each friend's identity/name/steam/presence under
every plausible field name (camelCase + PlayFab PascalCase, flat + nested profile)
under both friends/Friends keys, and log the JSON so we can pin the exact shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:06:17 +02:00
neckfire e5ab911dfd Merge pull request 'fortuna: FortunaPass2_Rewards catalog (claimable tiers)' (#14) from preprod into main
Build & Deploy / build (push) Successful in 34s
2026-07-15 20:17:37 +00:00
neckfireandClaude Opus 4.8 81092b077e fortuna: add FortunaPass2_Rewards catalog so tiers can be claimed
Build & Deploy / build (push) Successful in 50s
The claim function only grants a tier when a FortunaPass2_Rewards TitleData catalog
maps its rewardId (Level_N, sent by the client) to an item/amount. That catalog was
absent (client-side DataTable) so claims granted nothing and stayed unclaimable.
Add a Level_1..Level_100 catalog (K-Marks scaling, Insurance every 5, Aurum every 10)
so players can actually collect Fortuna pass rewards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:17:34 +02:00
neckfire 042f806697 Merge pull request 'docs: rewrite main README (dev + CI + client switch tool)' (#13) from preprod into main
Build & Deploy / build (push) Successful in 32s
2026-07-15 11:54:39 +00:00
neckfireandClaude Opus 4.8 95541b6221 docs: rewrite README around our build/CI + client switch tool
Build & Deploy / build (push) Successful in 32s
Replace the upstream Windows/local-run README with a dev-focused one: how the
client redirection works (loader+agent+backend.txt), repo structure, building
and running the API in a container, the TLS cert SAN gotcha, the Gitea CI/CD
branch model (preprod:preprod / main:latest), the Prospect.Client.Config
switch+cert tool, and an up-to-date feature status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:54:36 +02:00
neckfire a4c8cf5955 Merge pull request 'admin: fix empty tables (DT engine ordering)' (#12) from preprod into main
Build & Deploy / build (push) Successful in 33s
2026-07-15 09:52:00 +00:00
neckfireandClaude Opus 4.8 a8fca38038 admin: fix empty tables — define DT engine before page init scripts
Build & Deploy / build (push) Successful in 43s
The DT table engine lived in the page footer, after the per-page
<script>DT.init(...)</script> in the body, so DT was undefined when init ran
(ReferenceError, nothing rendered). Move the engine into a DtScript const
injected right after <main>, before any body content, so it is always defined
first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:51:58 +02:00
neckfire d1f3fd1125 Merge pull request 'admin: interactive tables (sort/filter/search/paging)' (#11) from preprod into main
Build & Deploy / build (push) Successful in 31s
2026-07-15 09:31:21 +00:00
neckfireandClaude Opus 4.8 b5c295f45c admin: interactive tables (sort/filter/search/paging) via client-side JS engine
Build & Deploy / build (push) Successful in 42s
Players and Catalog pages now fetch /admin/api/{players,catalog} JSON and render
a self-contained vanilla-JS data table: clickable column sort, live search,
dropdown filters (auth / category / rarity), page-size selector + prev/next
paging, result count. No CDN, works offline. Server-rendered dashboard,
player detail and titledata pages unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:30:22 +02:00
neckfire b05782d4e9 Merge pull request 'Admin: noms lisibles + catalogue (gameref)' (#10) from preprod into main
Build & Deploy / build (push) Successful in 29s
2026-07-15 09:19:14 +00:00
neckfireandClaude Opus 4.8 0b771c5447 feat(admin): resolve item/vanity ids to readable names + catalog browser
Build & Deploy / build (push) Successful in 42s
Bundle a compact id -> {name,rarity,category} lookup (Data/gameref.json, 1177 entries,
built from community-datamined TCF-Wiki data) and a GameRefService. The admin back-office
now shows readable names + coloured rarity for inventory items instead of raw ids, and a
new /admin/catalog page browses the known game catalog (searchable, by category).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:19:14 +02:00
neckfire d7c89327a7 Merge pull request 'Steam persona display names + admin backfill' (#9) from preprod into main
Build & Deploy / build (push) Successful in 29s
2026-07-15 08:25:53 +00:00
neckfireandClaude Opus 4.8 4614b135a1 feat: resolve player display names from Steam persona (Steam Web API)
Build & Deploy / build (push) Successful in 41s
Accounts were created as 'Unknown' and the client never sends a name, so the admin
panel / friends / squad showed no names. Add SteamWebApiService (GetPlayerSummaries),
wired into LoginWithSteam to backfill the DisplayName from the Steam persona when it's
still 'Unknown'. Admin gets a LAN-only /admin/backfill-names action (+ button) to
backfill existing accounts in one shot. Key read from config SteamWebApiKey (Vault ->
env), inert when unset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:25:52 +02:00
neckfire 03048b793d Merge pull request 'Back-office admin /admin (LAN-only)' (#8) from preprod into main
Build & Deploy / build (push) Successful in 32s
2026-07-15 07:56:34 +00:00
neckfireandClaude Opus 4.8 b8d023b02a feat(admin): read-only back-office at /admin (LAN-only)
Build & Deploy / build (push) Successful in 38s
Filament-style dark UI served directly by the API:
- Dashboard: player/entity/userdata/titledata counts + recent players
- /admin/players: searchable player list (name/id/steam)
- /admin/players/{id}: full player view — balance/faction/Fortuna cards, inventory
  table, and every UserData key pretty-printed (collapsible)
- /admin/titledata + /admin/titledata/{key}: browse in-memory title data
Restricted to LAN/loopback callers (RemoteIpAddress check) so it is never reachable
from the public internet even though :8443 is port-forwarded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:56:34 +02:00
neckfire 1d9cf795dd Merge pull request 'Audit léger: stats carrière, présence amis, AddGenericId' (#7) from preprod into main
Build & Deploy / build (push) Successful in 27s
2026-07-15 07:02:46 +00:00
neckfireandClaude Opus 4.8 0a68a97291 fix(audit léger): stats carrière, présence amis, AddGenericId
Build & Deploy / build (push) Successful in 43s
- GetPlayerStatisticsRequestsClient: renvoyait vide -> renvoie les stats demandées
  (0 pour l'instant, raid client-hosted non traçable) pour que l'écran carrière s'affiche.
- UpdatePlayerPresenceState: persiste {inMatch,lastSeenUtc}; GetFriendList lit la présence
  et calcule onlineState (online si vu <3min, 2=en raid) au lieu de 0 en dur.
- AddGenericId: persiste l'id de service lié (était un no-op).
(Free-loadout non touché: S3 only, pas de free-loadout en S2.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:02:45 +02:00
neckfire 7dbab1aab8 Merge pull request 'Audit fixes: Fortuna pass level, rename persist, loadout & craft-timer' (#6) from preprod into main
Build & Deploy / build (push) Successful in 26s
2026-07-15 00:57:11 +00:00
neckfireandClaude Opus 4.8 1b4c1b2606 fix(audit): Fortuna pass level, display-name persist, loadout & craft-timer queries
Build & Deploy / build (push) Successful in 39s
Audit of the emulator surface + 4 fixes:
- Fortuna pass level was frozen: nothing ever granted season XP. ClaimActiveContract
  now grants + persists FortunaPass{2,3}_SeasonXp (scaled to the contract's reputation)
  and returns the new total, so the pass level moves as you complete contracts.
- UpdateUserTitleDisplayName never persisted the rename (echoed back, lost on relog).
  Added DbUserService.UpdateDisplayNameAsync and call it.
- GetPlayerSets returned a blank loadout; now reads the persisted LOADOUT key.
- GetCraftingInProgressData was a stub; now returns the persisted CraftingTimer so an
  in-progress craft + remaining time survive a menu reopen/relog.

Remaining known gaps documented in Plane (need client data / multiplayer / out of scope).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:56:47 +02:00
neckfire 078d2f80be Merge pull request 'Fix critique: achats bloqués (sentinelle ContractLockPurchase None)' (#5) from preprod into main
Build & Deploy / build (push) Successful in 24s
2026-07-15 00:15:14 +00:00
neckfireandClaude Opus 4.8 14ab3267f4 fix(shop): don't treat ContractLockPurchase sentinel 'None' as a real lock
Build & Deploy / build (push) Successful in 34s
Regression from the contract-lock feature: the check gated on any non-empty
ContractLockPurchase, but the blueprint data uses the string "None" (not "")
for items with no lock (749/773 items). So virtually every item returned
'Item is locked' before the currency deduction -> players couldn't buy anything
(error shown, K-Marks unchanged). Skip the gate when the value is null/empty/"None".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:15:13 +02:00
neckfire c986a98471 Merge pull request 'Promotion: fix sauvegarde des missions en zone (raid client-hosted)' (#4) from preprod into main
Build & Deploy / build (push) Successful in 23s
2026-07-14 23:58:55 +00:00
neckfireandClaude Opus 4.8 dc34233e40 fix(contracts): persist raid-zone objective progress (client-hosted raids)
Build & Deploy / build (push) Successful in 36s
Symptom: missions done in raid zones weren't saved -> contracts stuck on
'Objective not met', no claim possible.

Root cause: client-hosted raids have no dedicated game server, so
UpdatePlayerActiveContracts is never called (confirmed: 0 calls in prod logs).
Only Kills and OwnNumOfItem objectives were handled; DeadDrop/VisitArea/
LootContainer had Progress[i]=0 forever -> unclaimable and not shown at station.

Fix: EYContractObjectiveType.IsRaidRuntime() groups the objectives that can only
be observed in a live raid (Kills/DeadDrop/VisitArea/LootContainer). Auto-credit
them on deploy (EnterMatchmakingMatch, persisted) so the station shows them
complete, and accept them in ClaimActiveContract. OwnNumOfItem still validated
against the real stash; FactionLevel/CompletedMission unchanged. Added a
defensive guard against a Progress array shorter than the objective list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 01:54:25 +02:00
neckfireandClaude Opus 4.8 f0343bc1ea gameserver: consolidate netcode R&D — remove diag spam, document DTLS-PSK wall
Build Game Server / build (push) Successful in 21s
- Keep the R3.5.0 header-bit fix (bunch alignment breakthrough)
- Remove per-packet [HS-RAW]/[HS-HDR]/[HS-BUNCH] diagnostics
- Document the DTLS-PSK encryption wall + packed-binary finding in NETCODE-RND.md
- UWorld NMT_Hello: honest comment on why the connection stops here

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 01:13:03 +02:00
neckfireandClaude Opus 4.8 ba9976c5b6 gameserver: skip encryption on Hello (try plaintext challenge)
Build Game Server / build (push) Successful in 19s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 20:54:01 +02:00
neckfireandClaude Opus 4.8 fb73038901 gameserver: consume Cycle's extra header bit -> bunches align (NMT_Hello parses)
Build Game Server / build (push) Successful in 22s
Bit-decoded real client packets: the header is 65 bits (not stock UE 4.27's 64).
One extra bit sits between the ack history and the packet-info payload; consuming
it realigns bHasPacketInfoPayload + jitter + bHasServerFrameTime, and the first
control-channel bunch parses (ChIndex 0, open, reliable = NMT_Hello).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 20:50:19 +02:00
neckfireandClaude Opus 4.8 629e8a4c31 gameserver: dump full packet bits (512) to decode NMT_Hello netver
Build Game Server / build (push) Successful in 18s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 20:29:11 +02:00
neckfireandClaude Opus 4.8 1691af4be6 gameserver: dump raw packet bit-stream for hand-decode
Build Game Server / build (push) Successful in 18s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:53:53 +02:00
neckfireandClaude Opus 4.8 b1386bef56 gameserver: bit-position diagnostics in ReceivedPacket (header + bunch flags)
Build Game Server / build (push) Successful in 17s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:41:39 +02:00
neckfireandClaude Opus 4.8 71aca5e3d0 fix(game-server): survive bad-packet exceptions in tick loop
Build Game Server / build (push) Successful in 17s
Wrap world.Tick in try/catch so an incompatible/misaligned client packet
logs an error and the server keeps running (stable R&D bench).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:30:12 +02:00
neckfireandClaude Opus 4.8 4d2280f8ab fix(game-server): don't crash on malformed bunch channel index
Build Game Server / build (push) Successful in 16s
Log + drop the packet (with net version) instead of throwing, so a
bit-alignment/version mismatch with the client doesn't kill the server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:26:53 +02:00
neckfireandClaude Opus 4.8 1caa46f846 fix(game-server): adopt client packet sequences on first packet
Build Game Server / build (push) Successful in 16s
The Cycle client does not derive its initial packet sequences from the
handshake cookie the way stock UE does (its acked server-seq isn't present
anywhere in the cookie), so the cookie-based InitSequence disagreed and
every post-handshake packet was rejected as out-of-order. On the first
packet, adopt the client's announced Seq/AckedSeq instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:21:49 +02:00
neckfireandClaude Opus 4.8 9092dda950 diag(game-server): log packet sequence mismatch (post-handshake)
Build Game Server / build (push) Successful in 21s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:08:52 +02:00
neckfireandClaude Opus 4.8 6ccad507a8 diag(game-server): log connectionless handshake (challenge/response)
Build Game Server / build (push) Successful in 16s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 19:00:19 +02:00
neckfireandClaude Opus 4.8 f4d1757840 diag(game-server): log stateless handshake packet size vs expected
Build Game Server / build (push) Successful in 15s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:52:52 +02:00
neckfireandClaude Opus 4.8 71a2c8a1e9 feat(deploy): optional dedicated-server travel via GAMESERVER_ADDRESS
Build & Deploy / build (push) Successful in 31s
When GAMESERVER_ADDRESS ("host:port") is set, EnterMatchmaking returns
that server (SingleplayerStation=false) and EnterMatchmakingMatch's
deploy signal carries its address, so the client travels to the real
game server instead of the client-hosted station. Unset = unchanged
(normal preprod). Lets a 2nd preprod run the same :preprod image and
differ only by this env var (dedicated-server test bench).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:41:20 +02:00
neckfireandClaude Opus 4.8 9749828af8 feat(game-server): scaffold dedicated server toward player join
Build Game Server / build (push) Successful in 17s
R&D branch for an authoritative Unreal game server (Prospect.Unreal).
The connection handshake already reaches PlayerController spawn; this
commit pushes it toward real players joining:

- Program.cs: target the Cycle station map + game mode (configurable via
  PROSPECT_MAP/GAMEMODE/PORT), proper host loop, and initialise a
  GameSession so NMT_Join -> Login no longer fails ("GameSession is null").
- UWorld.WelcomePlayer: send the world's real map/game mode to the client
  instead of the UE ThirdPerson template.
- Dockerfile.gameserver + .gitea/workflows/game-server.yml: build/push a
  separate image (the-cycle-game) on the game-server branch, ntfy notify.
- GAMESERVER.md: honest state + roadmap (player spawn + movement
  replication need live client testing; AI/loot intentionally deferred).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:30:09 +02:00
neckfireandClaude Opus 4.8 55a9571958 fix(matchmaking): deploy signal targets only the deploying player
Build & Deploy / build (push) Successful in 32s
EnterMatchmakingMatch broadcast OnSquadMatchmakingSuccess to Clients.All,
so one player deploying pulled every connected player into the match.
Map each player to their SignalR connection (GetSignalRConnection tags the
URL with uid; CycleHub records it via a SignalRConnectionRegistry) and
send the deploy signal only to that connection (broadcast fallback if
unmapped). Also enable m_automaticImportPlatformFriends (Steam friends).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:56:22 +02:00