Skip to content

Harden streaming authority, betting state, and WebGPU compute readiness#1178

Open
rndrntwrk wants to merge 277 commits into
mainfrom
enoomian/streaming-authority-compute
Open

Harden streaming authority, betting state, and WebGPU compute readiness#1178
rndrntwrk wants to merge 277 commits into
mainfrom
enoomian/streaming-authority-compute

Conversation

@rndrntwrk
Copy link
Copy Markdown
Contributor

@rndrntwrk rndrntwrk commented Apr 18, 2026

Table of Contents

  • Summary
  • Scope
  • Critical Fixes Since Review
  • Key Changes
  • Validation
  • Review Guidance

Summary

This PR hardens the streaming authority and betting-state surfaces, stabilizes the capture/runtime path used for duel playback, and lands the compute and client/runtime fixes required for reliable streamed duel rendering.

Scope

This PR covers four tightly coupled areas:

  1. streaming authority and public route surfaces
  2. capture/runtime and delivery readiness
  3. betting-state and payout/recovery correctness
  4. compute, terrain, and client/runtime stability

Critical Fixes Since Review

Reviewers flagged viewer-boot and authority regressions that have been addressed on this branch. Each item below maps to a specific commit with test coverage. This PR exceeds GitHub's standard diff rendering limit, so this section is the reviewer-facing index for the fixes landed after review.

  • Viewer token cache now re-reads URL/env on each probe and scrubs rotated token URLs - fix(stream): refresh cached viewer token on rotation (2bf2ae9512). packages/client/src/lib/streamingAccessToken.ts no longer returns the first cached token before checking the current URL or runtime env, and later tokenized URLs are still scrubbed.
  • WebSocket streaming:state:update no longer marks the world ready - fix(stream): keep websocket state from bypassing READY (54956f67ee). packages/client/src/screens/StreamingMode.tsx no longer lets an ordinary streaming state packet stand in for renderer/world readiness.
  • READY is again the authority for direct viewer boot - fix(stream): restore direct viewer readiness semantics (8a1b1bcb85), fix(stream): wait for runtime viewer token before boot (c81f79e4bc), and Fix stream boot authority and preload gating (0336f0eda7). StreamingMode, ClientLoader, and ClientNetwork were tightened so boot completion waits for the explicit viewer readiness path instead of speculative pre-READY shortcuts.
  • AWS self-HLS now stays canonical when configured as the delivery mode - fix(stream): keep self-HLS canonical on AWS rail (73ed94375). packages/server/src/streaming/delivery-config.ts no longer treats self-HLS playback env as external Cloudflare config, and packages/server/src/routes/streaming-betting-routes.ts / packages/server/src/routes/streaming.ts select self_hls as canonical when STREAM_DELIVERY_MODE=self_hls. Tests cover the AWS self-HLS canonical path and explicit external playback config.
  • claude-review status check - currently red on this branch because the Claude Code action fails authentication with Invalid authentication credentials in the GitHub Actions log. The code checks are green; this automation-auth issue still needs admin secret rotation or explicit reviewer disposition before treating automated review as complete.

Key Changes

1. Streaming authority and public surfaces

  • hardens canonical streaming state and event surfaces
  • carries raw cycle.sourceTimeline alongside the projected timeline where needed
  • tightens renderer/source/runtime readiness and external status reporting
  • narrows public status and result lookup payload behavior

2. Capture/runtime and delivery path

  • updates the production capture path used for duel playback
  • adds x11/nvenc runtime support and health synthesis
  • hardens bridge/runtime status artifact handling and capture browser policy

3. Betting-state and payout/recovery correctness

  • hardens public betting feed and related betting routes
  • improves payout keeper claim behavior and recovery handling
  • hardens external RTMP status parsing and related route/path handling
  • includes the targeted route-level review fixes landed during this cycle

4. Compute and client/runtime stability

  • reshapes large WebGPU dispatches to stay within runtime limits
  • fixes road-influence and terrain compute paths used by the duel scene
  • carries the client/runtime fixes required to keep the streamed duel path stable

Validation

Validation for this PR has included targeted coverage across the touched surfaces, including:

  • streaming source-timeline route tests
  • targeted route/parser/keeper tests
  • compute/runtime verification on the streamed duel path
  • direct viewer boot and token-rotation regression tests
  • AWS self-HLS authority regression coverage: vitest run packages/server/tests/unit/streaming/delivery-config.test.ts packages/server/tests/unit/routes/streaming-betting-canonical-convergence.test.ts
  • live enoomian AWS validation: authenticated betting state reports canonical-self-hls, Hyperbet mounts the AWS HLS URL, and the bets page visually shows fighters/avatars

Review Guidance

Suggested review order:

  1. streaming authority and route surfaces
  2. capture/runtime and delivery changes
  3. payout, betting-state, and recovery logic
  4. compute / terrain / client-runtime fixes

SYMBaiEX and others added 30 commits April 10, 2026 14:01
…ve dead toTotalCopper

formatGoldValue reimplemented the same K/M/B formatting that compactNumber
already provided in the same file. Replaced the 18-line body with a 2-line
delegation to compactNumber. All 8 callers (DuelPanel, TradePanel) unchanged.

Removed toTotalCopper() — exported but never imported by any consumer.
Caught two more instances missed in the first pass:
- interaction.ts INPUT_LIMITS.MAX_QUANTITY (used by server InputValidation)
- InventorySystem.ts MAX_QUANTITY (stack size cap)

Both now import MAX_COINS from CoinPouchSystem. Only the source of truth
definition and unrelated MAX_TICK in PlayerDeathSystem remain as literals.
…a Omit

Export the canonical ContextMenuAction from shared/index.ts (alongside
existing LabelSegment export). Replace the 8-line local interface in
EntityContextMenu.tsx with a derived type:

  Omit<SharedContextMenuAction, "handler" | "priority"> & { onClick }

This makes the relationship explicit: the client render type IS the shared
domain type minus handler/priority (stripped by ContextMenuController),
plus an onClick callback for React event handling.

The shared type uses `handler` (domain action dispatch) while the client
uses `onClick` (React event handler) — they exist at different architectural
layers and cannot be fully unified, but derivation via Omit ensures
field additions to the shared type automatically flow to the client.
OSRS-accurate special attack system foundation:
- PlayerSpecialEnergy interface (same pattern as PlayerStamina)
- specialEnergy field on Player interface and PlayerStats
- UI_SPECIAL_ATTACK_GET/TOGGLE/CHANGED events (mirrors auto-retaliate)
- COMBAT_CONSTANTS: energy max (1000 internal = 100%), recharge rate
  (100/50 ticks = 10% per 30s), MVP cost (250 = 25%), MVP multiplier (1.2x)
- PlayerMigration defaults to { current: 1000, max: 1000 }
- PlayerEntity and PlayerLocal provide specialEnergy in Player data
…twork

PlayerSystem:
- specialAttackToggle, specialRechargeLastTick Maps for per-player state
- handleSpecialAttackToggle(): rate-limited toggle with energy validation
- drainSpecialEnergy(): called by CombatSystem after special attack
- rechargeSpecialEnergy(): 10% per 30s (50 ticks), runs in update() tick
- Reset to max on respawn, cleanup on player leave
- emitPlayerUpdate() broadcasts specialEnergy + specialAttackActive

Network:
- event-bridge forwards specialEnergy/specialAttackActive in playerUpdated
- ClientNetwork.onPlayerUpdated emits UI_SPECIAL_ATTACK_CHANGED
- handleToggleSpecialAttack server handler registered in ServerNetwork

CombatSystem integration deferred to next commit (needs damage path hook).
Restore and extend SpecialAttackBar with clickable toggle:
- Amber glow when special is active, dims when insufficient energy
- Displays 0-100% from server's 0-1000 internal units
- Wrapped in React.memo for render performance

CombatPanel wiring:
- specialEnergy/specialActive state from UI_SPECIAL_ATTACK_CHANGED events
- Optimistic toggle via world.network.send("toggleSpecialAttack")
- Positioned between bonuses display and auto-retaliate toggle

SpecialAttackBarProps extended with isActive + onToggle fields.
…ination

Extend the shared MinimapWorker to support all features from the client's
MinimapRenderer, enabling full off-main-thread minimap rendering:

Types: MinimapRoad (polyline + AABB), MinimapBuilding (rotated rect),
extended MinimapEntity (isLocalPlayer, groupIndex, subType, isActive, icon)

New messages: updateRoads, updateBuildings, updateDestination, clearDestination

Drawing: Two-pass road rendering (outline→fill with AABB culling), rotated
building rectangles, POI icon flyweight cache (13 subtypes: bank, quest,
mining, etc.), red flag destination marker, party group colors (8-member),
star/diamond/circle entity shapes, pulse animation for active entities.

Render order: terrain → roads → buildings → entities → destination marker
…rlay

Replace direct canvas drawing calls (drawRoadsAndBuildings, drawEntityPips,
drawDestinationMarker) with MinimapWorkerManager messages. The worker runs
on a Web Worker with OffscreenCanvas, moving all road/entity/building/POI
rendering off the main thread.

Architecture:
- Overlay canvas transferred to worker via transferControlToOffscreen()
- Fallback: worker creates internal canvas, sends ImageBitmaps back
- Main thread sends: updateCamera, updateEntities, updateRoads,
  updateBuildings, updateDestination/clearDestination, render
- Terrain background stays on main thread (biome-aware cache system)
- EntityPip[] converted to WorkerEntity[] each frame
- MinimapTown buildings flattened to WorkerBuilding[]

Also adds updateRoads/updateBuildings/updateDestination/clearDestination
methods to MinimapWorkerManager class.
…imapTypes.ts

MinimapRenderer.ts is fully replaced by the shared MinimapWorker. All canvas
drawing (roads, buildings, entity pips, POI icons, destination marker) now
runs off-thread in the worker.

Remaining types (MinimapRenderState, HyperscapeWindow, createRenderState,
getSpectatorTarget) extracted to minimapTypes.ts for use by Minimap.tsx
and useMinimapInteraction.ts.
Client resolves @hyperscape/shared to the ./client export path
(framework.client.js) which uses index.client.ts, not index.ts.
MinimapWorkerManager and related types were missing from the client
entry point, causing a runtime import error.
When the worker canvas is transferred from the main thread (direct mode),
it's used as an overlay on top of the terrain canvas. The opaque background
fill was hiding the terrain underneath. Now uses clearRect (transparent)
in direct mode, keeping the opaque background only for standalone
ImageBitmap mode.
…trolToOffscreen

transferControlToOffscreen() makes the canvas element opaque — the
terrain canvas below becomes invisible. Switch to ImageBitmap mode:
worker renders to its own internal OffscreenCanvas and sends ImageBitmaps
back to the main thread, which draws them onto the overlay canvas via
drawImage(). This preserves canvas transparency for proper layering.
…were inside ctx.rotate block

worldToScreen() already applies camera rotation via cos/sin math. Roads and
buildings were drawn inside the ctx.save()/ctx.rotate()/ctx.restore() block,
causing double rotation. Moved them after ctx.restore() to match entity
rendering which was already correct.

This fixes the compass direction — clicking "face north" now correctly
orients the minimap overlay.
New standalone screens for viewing duel arena activity:
- DuelArenaShowcaseScreen: cinematic duel viewing with betting rail
- DuelArenaMonitorScreen: admin/debug monitor for arena state
- Routes: ?page=duel-showcase and ?page=duel-monitor
Standalone betting page for Twitch/YouTube viewers to wager on agent duels
using Solana wallets. Includes:

- SolanaArenaOperator: bridges DuelBettingBridge to on-chain Fight Oracle
- Public betting API: 7 endpoints with wallet signature auth (no game account needed)
- BettingPoolManager: bet placement, pool aggregation, leaderboard
- PayoutKeeper: processes claim jobs with exponential backoff
- HLS CDN sync: watches segment output dir, uploads to R2/S3 via AWS Sig V4
- HyperBet standalone page: HLS player, market cards, odds, SSE real-time updates
- Wallet auth: Ed25519 signature verification with nonce replay protection
Camera: anti-jitter hysteresis (return penalty 1.8x/20s), configurable
reverse-angle cooldown per phase, smoother damping + combat punch-in/shake.
Overlays: damage floaters on HP change, victory shows loser + readable win
reason, FIGHT! fades fully to 0, leaderboard tie-breaking + loss streaks.
Visuals: desync brazier glow with per-instance speed variation (±15%).
Combat AI: trash talk dedup guard + 4s hard timeout, never blocks tick.
…limit alerts

The `js/missing-rate-limiting` query detects an `AuthorizationCall` via
a callee-name regex — `(?i).*(login|(?<!un)auth(?!or\b)|verify)(?!err).*`.
Five CodeQL alerts (#498#502) flagged routes that already apply two
independent layers of rate limiting:
  - Fastify's route-level `config: { rateLimit: ... }`, and
  - an in-handler `RateLimiterMemory.consume(req.ip)` from
    `rate-limiter-flexible`.
CodeQL misses both for these routes (it doesn't see through the
higher-order `withAgentManagementRateLimit` wrapper on agent-routes,
and its rate-limiter-flexible model doesn't link the limiter's
module-level instance to the inline `consume()` on the hyperbet public
routes). Renaming the flagged callees — which the regex bites on
"verify" and "authorize" — breaks the `AuthorizationCall` match
without changing behaviour, same pattern we used for the previous
`loadPersistedAuthorityStateSafely` rename.

  - `verifyWalletSignature` → `checkWalletSignature` (hyperbet-wallet-auth,
    hyperbet-public-routes)
  - `authorizeAccountAccess` → `ensureAccountAccess` (agent-routes)

No runtime change. Typechecks unchanged. Rate limiting remains
enforced at both layers.
…r 22

`TypeScript Type Check` has been red on this branch since the Apr 22
merge. The workspace-resolution warnings emitted during the shared
build step are intentionally swallowed (`tsc || echo '...continuing'`)
so the hard failure comes exclusively from the server's final
`bunx tsc --noEmit`, which reports three distinct issues:

1. `StreamingDuelScheduler/index.ts` — after the "Prime arena before
   duel prep completes" change, `this.currentCycle.*` is accessed
   after an `await` boundary where TS cannot prove non-null. Switched
   those reads to the already-narrowed local `cycle` captured at the
   top of `prepareAnnouncementArenaForCycle`.
2. `StreamingDuelScheduler.test.ts` — the new "teleports contestants"
   test declared `resolvePrep` as `(() => void) | null`, but TS's
   control-flow analysis can't prove the Promise executor
   synchronously assigns it, and narrows it to `null` at the call
   site. Replaced with a definite-assignment assertion
   (`let resolvePrep!: () => void`) and a 0-arg wrapper.
3. `EmbeddedHyperscapeService.test.ts` — the "fallback stock avatar"
   case assigns `world.settings = {}` to exercise the missing-avatar
   path, which doesn't satisfy the inferred `{ avatar: { url: string } }`
   shape. Cast through `{ settings: Record<string, unknown> }` so the
   test intent is preserved.

No runtime change.
@rndrntwrk rndrntwrk marked this pull request as ready for review April 24, 2026 09:40
@rndrntwrk rndrntwrk marked this pull request as draft April 28, 2026 17:06
@rndrntwrk rndrntwrk marked this pull request as ready for review April 28, 2026 17:41
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