Fow web UI - #5438
Draft
niugnip wants to merge 8 commits into
Draft
Conversation
Custom hyperlane tiles ("hl") carry their connection layout in
per-position data rather than in the tile artwork, so the web UI has no
way to draw them without it. Exposes the matrix CustomHyperlaneService
already computes for the Discord renderer.
Serves a per-viewer filtered view of the game: tiles outside current vision are replaced with the viewer's remembered "ghost" snapshot, unidentified players' unit/control-token faction identity is obscured behind a "fow:<color>" sentinel while keeping the real color, and player stats, score breakdowns, objective progress, elected laws and expedition completers are redacted for players the viewer can't identify. Also populates the per-planet exhausted flag for on-tile planets, which the web UI previously had to derive from each player's exhaustedPlanets list - unavailable for unidentified players. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/events previously 404'd unconditionally for any FoW game, with no viewer check at all - the GM couldn't see it either. Extracts the existing owner/GM-role check (already used by /web-data-fow) into a shared isGm helper and allows the GM/owner through. Also drops the "physical board" framing from a few FoW comments - FoW+ implements plenty that wouldn't exist on a physical board. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The neutral (Dicecord) player isn't a "real player" per FoWHelper#canSeeStatsOfPlayer, so it was falling through every identity check as if it were a hidden opponent - excluded from FoW playerData entirely, and its units/control tokens obscured with the "fow:<color>" sentinel. Neutral forces are public knowledge, not a hidden opponent, so those checks now special-case it through.
Registers the admin/developer/bothelper role IDs for niugnip's server, following the existing per-server pattern in JdaService.
Temporary rollout gate while the new Fog of War web UI is tested: in production, the FoW web-data and event-log endpoints require one of the FoW server's staff roles (server, Game Supervisor, and the Chapter 2 bothelper/supervisor equivalents). GMs are not exempt - most hold one of these already, and exempting them would leave nearly every FoW game reachable, which defeats the point. Scoped to that server's own roles rather than reusing bothelperRoles, which pulls in every Async and community server plus all admins and developers - far wider than this gate is meant to be. Only applies when JdaService#isProduction(), so local and dev bots are unaffected and need no role. Fails closed: unresolvable roles are skipped and access is denied rather than treating "couldn't check" as a pass. Adds JdaService#hasAnyRole(userId, roleIds...), since the existing role checks need an interaction event and the web API only has a Discord user ID. It reads JDA's member cache (populated eagerly via MemberCachePolicy.ALL / ChunkingFilter.ALL), so it stays a synchronous lookup rather than a REST call on the request thread. Also relabels role 1063464689218105354 from "FoW Server Game Admin" to "FoW Server Game Supervisor", matching what the role is actually called now. Revert this commit to open the UI up.
Under fog you learn that someone scored an objective, not who - but the payload was sending every scorer's real faction and leaving it to the client to render the unidentifiable ones as anonymous tokens. The answer was sitting in the JSON the whole time. Auditing the rest of the payload for the same shape turned up three more fields with no redaction at all. - objectives: unidentifiable scorers are removed from scoredFactions and replaced by a bare unidentifiedScorerCount; peekingFactions is filtered the same way. - scoreBreakdowns: hidden players are left out entirely rather than carrying a faction-keyed, SCORED-only breakdown that spelled out their whole scoring history. - hiddenPlayerVps: their score-track totals instead travel as a sorted list of bare numbers - enough to place an anonymous token on the track, with nothing tying it to a faction. - strategyCards: pickedByFaction is cleared unless the viewer can see that player's stats, and an unplayed card no longer reports that it was picked at all. Playing a card is announced, so played (and the pick it implies) stays visible. - statTilePositions: entries are dropped for players whose stats the viewer can't see. The tiles sit beside a home system, so a faction-keyed map gave away both who was in the game and roughly where. With no faction strings reaching the client there is nothing to correlate across objective cards, so the per-objective shuffle that existed to frustrate that correlation is gone too. Verified against a running bot with a player forced unidentifiable: their faction no longer appears anywhere in the fogged payload.
Review follow-up: canIdentify and the stat-tile redaction kept a faction in the payload when it could not be resolved to a player, while redactPickers redacted the same case. A redaction layer should treat "couldn't resolve" like "couldn't check" in the rollout gate: deny rather than pass. All three now fail closed. Also reattaches the redactFactionProgress javadoc, which the redactScorers insertion had left stacked on the wrong method. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Fog of War support to the backend payload that feeds the new web UI, so FoW games can be viewed in it at all. Until now the new UI was hard-blocked for FoW games and players were sent to the old image-based view.
Pairs with AsyncTI4/ti4_web_new#138— this needs to merge first, or the frontend reads fields that don't exist yet.
What it does
/web-data-fowserves a per-viewer filtered payload:fow:<color>sentinel — the real color still renders (matchingDrawingUtil#drawControlToken, which draws the colored token but skips the faction seal), but the faction behind it doesn't.unidentifiedScorerCount, their score breakdowns are omitted entirely (score-track totals travel as faction-lesshiddenPlayerVps), strategy-card picks are cleared unless the viewer can see that player's stats (with trade goods blanked so unpicked cards can't be identified by elimination), and stat-tile positions are dropped for them.exhaustedis now populated for on-tile planets. The web UI previously derived this from each player'sexhaustedPlanetslist, which isn't available for unidentified players, so exhausted planets in visible systems rendered as ready.The GM/owner gets the unfiltered payload, and can preview any player's fogged view via
?asPlayer=for debugging.X-Viewer-Is-Gmdistinguishes "GM previewing a player" from "that player's own view", which are otherwise identical in shape.FoW games stream only a
"refresh"signal over the websocket rather than a patch — the payload differs per viewer, so clients refetch and the fog is re-applied server-side.Commits
Reviewable in order; each builds standalone.
FoWHelper#canSeeStatsOfPlayer, so it was failing every identity check as though it were a hidden opponent. Also counts neutral production, which affects non-FoW games too, where it simply wasn't counted before.The rollout gate
The gate commit limits the FoW web-data and event endpoints to holders of one of the FoW server's staff roles (server, Game Supervisor, and the Chapter 2 bothelper/supervisor equivalents), in production only (
JdaService#isProduction()), so dev bots are unaffected. GMs are not exempt — most hold one of these already, and exempting them would leave nearly every FoW game reachable.Deliberately scoped to that server's own roles rather than reusing
bothelperRoles, which spans every Async and community server plus all admins and developers — far wider than this gate is meant to be.It fails closed: unresolvable roles are skipped and access is denied rather than treating "couldn't check" as a pass. Revert that single commit to lift the restriction — nothing else depends on it.
This also adds
JdaService#hasAnyRole(userId, roleIds...), since the existing role checks need an interaction event and the web API only has a Discord user ID. It reads JDA's member cache, which is populated eagerly (MemberCachePolicy.ALL/ChunkingFilter.ALL), so it stays a synchronous lookup rather than a REST call on the request thread.It also relabels role
1063464689218105354from "FoW Server Game Admin" to "FoW Server Game Supervisor", matching what that role is actually called now.Testing
Full
mvn installclean: 327 tests, 0 failures, 0 errors, SpotBugs and Spotless clean. Exercised manually against a local FoW save as GM, as a participant, and via GM preview-as-player. The identity withholding was additionally verified against a running bot with a player forced unidentifiable: their faction no longer appears anywhere in the fogged payload.The gate itself was exercised against a running bot by temporarily forcing it on and substituting a role this test bot can see: a user holding the role got the payload, while a user without it — including an actual participant in the game — got a 403. That confirms the lookup resolves a role from a non-primary guild, hits the member cache synchronously rather than making a REST call, and sits above the participant check rather than only the GM branch.
Not verified: that the four production role IDs themselves resolve, since this bot isn't in the FoW server. The mechanism is proven; only those specific IDs aren't. Worth a look at the first deploy, given it fails closed.