Harden streaming authority, betting state, and WebGPU compute readiness#1178
Open
rndrntwrk wants to merge 277 commits into
Open
Harden streaming authority, betting state, and WebGPU compute readiness#1178rndrntwrk wants to merge 277 commits into
rndrntwrk wants to merge 277 commits into
Conversation
…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.
…erlay compositing
…th instead of north)
…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.
…streaming-authority-compute
…streaming-authority-compute
…streaming-authority-compute
…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.
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.
Table of Contents
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:
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.
fix(stream): refresh cached viewer token on rotation(2bf2ae9512).packages/client/src/lib/streamingAccessToken.tsno longer returns the first cached token before checking the current URL or runtime env, and later tokenized URLs are still scrubbed.streaming:state:updateno longer marks the world ready -fix(stream): keep websocket state from bypassing READY(54956f67ee).packages/client/src/screens/StreamingMode.tsxno longer lets an ordinary streaming state packet stand in for renderer/world readiness.READYis 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), andFix stream boot authority and preload gating(0336f0eda7).StreamingMode,ClientLoader, andClientNetworkwere tightened so boot completion waits for the explicit viewer readiness path instead of speculative pre-READY shortcuts.fix(stream): keep self-HLS canonical on AWS rail(73ed94375).packages/server/src/streaming/delivery-config.tsno longer treats self-HLS playback env as external Cloudflare config, andpackages/server/src/routes/streaming-betting-routes.ts/packages/server/src/routes/streaming.tsselectself_hlsas canonical whenSTREAM_DELIVERY_MODE=self_hls. Tests cover the AWS self-HLS canonical path and explicit external playback config.claude-reviewstatus check - currently red on this branch because the Claude Code action fails authentication withInvalid authentication credentialsin 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
cycle.sourceTimelinealongside the projected timeline where needed2. Capture/runtime and delivery path
3. Betting-state and payout/recovery correctness
4. Compute and client/runtime stability
Validation
Validation for this PR has included targeted coverage across the touched surfaces, including:
vitest run packages/server/tests/unit/streaming/delivery-config.test.ts packages/server/tests/unit/routes/streaming-betting-canonical-convergence.test.tscanonical-self-hls, Hyperbet mounts the AWS HLS URL, and the bets page visually shows fighters/avatarsReview Guidance
Suggested review order: