Playtest #3 fixes: lost models, picking, live LP, temp overflow, 蕾缪安 S3, 回环射手, FX, detail card, spinners

- #1 lost operator models: the Spine store no longer hands out a skeleton whose unload is in flight
  (PIXI.Assets.unload is async); eviction runs 1 s after a release and the empty-scene budget only after 3 s
- #7 picking: one shared rule (render/pick.js, pixel probe where bodies overlap) for prep/battle clicks,
  hover, the enemy pen and equipment drops; the drag ghost stands on its drop tile; hover probes capped
  at one per frame
- #2 the top-bar LP drops live as the own battle's enemies enter the blue gate (−N, 联防中), teammates'
  pending LP in the team panel
- #3 temp overflow pieces that arrive after a prep deadline survive through the next prep (tempDue);
  temp-row warning and ready-button reason
- #4 蕾缪安 S3: locks every 0.5 s, then one shell every 0.3 s in lock order (PRTS notes), bombardShell fx
- #5 回环射手: real boomerang (out 15, back 3.75 tiles/s, attack only when every boomerang is caught)
- #6 visible projectiles, muzzle flashes, slashes, skill bursts and auras at unchanged frame cost
- #8 特质 right under the detail card header, with its official type icon
- #9 no spinning busy cursors / spinners next to texts; game data warmed before the match
- docs, recordings and tests updated (2414 unit tests, all browser suites green)
This commit is contained in:
sganggs committed 2026-09-29 23:42:55 +08:00
1 parent 01a44cd288
commit 0aba602ecf
72 files changed
+5132 -571

No files matched your search

+18
View File
@@ -21,6 +21,8 @@
// re-layout; ui/compat.js polyfills are imported before anything else.
// 干员调配 (DESIGN §16): an overlay over any route (<LoadoutHost/>, opened from lobby / room / briefing); its loadout is
// kept in sync with the server by installLoadoutSync (room.loadout after every welcome and edit).
// Game data: every text of the game is static data (/data/*.json) downloaded once per page; the in-match files are
// warmed in the background as soon as the player is in a room (warmGameData), before the match needs them.
// Polyfills first (older Safari / Firefox ESR): every module evaluated after this one sees them.
import './ui/compat.js';
@@ -32,6 +34,7 @@ import { ToastHost, toast, toastError, describeError } from './ui/toasts.js';
import { net, identity, NetError } from './net.js';
import { store, useStore, emptyMatch, selectRoute, sessionResetNotice } from './store.js';
import { data } from './data.js';
import { GAME_FILES } from './ui/gameComponents.js';
import { TitleScreen, sanitizeName } from './screens/title.js';
import { LobbyScreen, rememberRoom, parseRoomParam } from './screens/lobby.js';
import { RoomScreen } from './screens/room.js';
@@ -223,9 +226,24 @@ function wireNet() {
// Entering (title → lobby) while already online also needs the deep-link join.
store.subscribe((s, prev) => {
if (s.session.entered && !prev.session.entered) schedulePendingJoin();
// in a room (co-op or solo, also a resumed one) a match is near: its data starts downloading
if (s.room && !prev.room) warmGameData();
});
}
/**
* Download every data file of the match UI (gameComponents GAME_FILES: operators, skills, bonds, items, enemies, 特质 …)
* in the background once the player is in a room — a match is near (the lobby alone never downloads them). The game's
* texts are static data loaded once per page — never fetched during a match — and the match screen waits for these
* files, so with them warmed it opens at once and no text ever appears late (user playtest #3 item 9). Idempotent (the
* data store shares each file's promise).
*/
function warmGameData() {
const go = () => { data.loadAll(GAME_FILES).catch(() => {}); };
if (typeof globalThis.requestIdleCallback === 'function') globalThis.requestIdleCallback(go, { timeout: 2500 });
else setTimeout(go, 600);
}
// ---- UI chrome (the connection banner lives in ui/connBanner.js) -----------------------------------
function ScreenCrashed({ error, reset }) {