fix(client): fail fast on boot/frontend fallbacks (P4)

- MapService load failure is no longer swallowed.
- Character creation fails on unknown selected class instead of ?? 1.
- No prefilled 'Wanderer'/'password' credentials; empty account name is an
  error; unknown MCP class id throws instead of ?? 'so'.
- CommandMapper no longer sends BeltUse with fabricated itemId slot+1.
This commit is contained in:
troytt 2026-10-01 01:41:14 +00:00
parent 7db8c3c9b8
commit 3d20dc9c77
3 changed files with 17 additions and 20 deletions

View File

@ -253,8 +253,9 @@ export function createInitialFlowState(initialRoster: RosterCharacter[] = []): B
stageTicks: 0, stageTicks: 0,
globalTick: 0, globalTick: 0,
accountName: 'Wanderer', // No prefilled credentials: the user (or a configured bot) must type them.
accountPassword: 'password', accountName: '',
accountPassword: '',
loginError: null, loginError: null,
campfireClasses, campfireClasses,
@ -338,7 +339,8 @@ export function cancelConnecting(state: BnetFlowState): void {
export function submitBattleNetLogin(state: BnetFlowState): boolean { export function submitBattleNetLogin(state: BnetFlowState): boolean {
if (state.stage !== 'bnet_login') return false if (state.stage !== 'bnet_login') return false
if (!state.accountName.trim()) { if (!state.accountName.trim()) {
state.accountName = 'Wanderer' state.loginError = 'Account name required'
return false
} }
state.loginError = null state.loginError = null
state.stageTicks = 0 state.stageTicks = 0
@ -589,7 +591,8 @@ export function charSummaryToRosterCharacter(summary: {
readonly hardcore: boolean readonly hardcore: boolean
readonly ladder: boolean readonly ladder: boolean
}): RosterCharacter { }): RosterCharacter {
const classId = CHAR_CLASS_ID_TO_CAMPFIRE[summary.charClass] ?? 'so' const classId = CHAR_CLASS_ID_TO_CAMPFIRE[summary.charClass]
if (!classId) throw new Error(`MCP character "${summary.name}" has unknown class id ${summary.charClass}`)
const spec = CAMPFIRE_CLASS_SPECS[classId] const spec = CAMPFIRE_CLASS_SPECS[classId]
return { return {
id: `${spec.heroToken}-${summary.name}`, id: `${spec.heroToken}-${summary.name}`,

View File

@ -634,18 +634,9 @@ export class CommandMapper {
if (event.key === '1' || event.key === '2' || event.key === '3' || event.key === '4') { if (event.key === '1' || event.key === '2' || event.key === '3' || event.key === '4') {
const slotIndex = Number(event.key) - 1 const slotIndex = Number(event.key) - 1
event.preventDefault?.() event.preventDefault?.()
if (this.hudModel) { // BeltUse needs the real server item id of the potion, which only HudModel (ClientWorld) knows.
if (!this.hudModel) return false
this.hudModel.useBeltSlot(slotIndex, Boolean(event.shiftKey)) this.hudModel.useBeltSlot(slotIndex, Boolean(event.shiftKey))
} else {
this.dispatchRateLimited(
{
type: 'BeltUse',
itemId: slotIndex + 1,
shift: Boolean(event.shiftKey),
},
nowMs,
)
}
return true return true
} }

View File

@ -193,11 +193,12 @@ async function bootPlayApp(): Promise<PlayAppController> {
}) })
await hudManager.load('/ui') await hudManager.load('/ui')
let mapService: MapService | null = null // Fail fast: without the packed act tiles there is no 1.13c map (automap, collision, picking).
let mapService: MapService
try { try {
mapService = await MapService.fromPackedUrl('/act-packs/tiles') mapService = await MapService.fromPackedUrl('/act-packs/tiles')
} catch { } catch (err) {
mapService = null throw new Error(`MapService: failed to load /act-packs/tiles: ${err instanceof Error ? err.message : String(err)}`)
} }
// Kick off asynchronous GPU atlas preloading for WebGL2 world rendering // Kick off asynchronous GPU atlas preloading for WebGL2 world rendering
@ -468,9 +469,11 @@ async function bootPlayApp(): Promise<PlayAppController> {
dz: 5, dz: 5,
as: 6, as: 6,
} }
const classId = st.selectedClass === null ? undefined : classMap[st.selectedClass]
if (classId === undefined) throw new Error(`Unknown selected class ${String(st.selectedClass)}`)
const chars = await session.createCharacter({ const chars = await session.createCharacter({
name, name,
classId: classMap[st.selectedClass] ?? 1, classId,
hardcore: st.isHardcore, hardcore: st.isHardcore,
expansion: st.isExpansion, expansion: st.isExpansion,
ladder: st.isLadder, ladder: st.isLadder,