// Phase 28 — FBS BattleReport payload builder used by Battle Viewer // Playwright specs. Mirrors the `user.games.battle` ConnectRPC // response that the gateway re-encodes from the engine's JSON. import { Builder } from "flatbuffers"; import { BattleActionReport, BattleReport, BattleReportGroup, RaceEntry, ShipEntry, TechEntry, UUID, } from "../../../src/proto/galaxy/fbs/battle"; export interface BattleGroupFixture { key: number; race: string; className: string; tech?: Record; num: number; numLeft: number; loadType?: string; loadQuantity?: number; inBattle?: boolean; } export interface BattleActionFixture { a: number; sa: number; d: number; sd: number; x: boolean; } export interface BattleFixture { id: string; planet: number; planetName: string; races: Record; ships: BattleGroupFixture[]; protocol: BattleActionFixture[]; } export function buildBattlePayload(fixture: BattleFixture): Uint8Array { const builder = new Builder(512); const planetNameOff = builder.createString(fixture.planetName); const raceOffsets: number[] = []; for (const [keyStr, value] of Object.entries(fixture.races)) { const key = Number(keyStr); const [hi, lo] = uuidToHiLo(value); RaceEntry.startRaceEntry(builder); RaceEntry.addKey(builder, BigInt(key)); RaceEntry.addValue(builder, UUID.createUUID(builder, hi, lo)); raceOffsets.push(RaceEntry.endRaceEntry(builder)); } const racesVec = BattleReport.createRacesVector(builder, raceOffsets); const shipOffsets: number[] = []; for (const ship of fixture.ships) { const techOffsets: number[] = []; for (const [tk, tv] of Object.entries(ship.tech ?? {})) { const tkOff = builder.createString(tk); TechEntry.startTechEntry(builder); TechEntry.addKey(builder, tkOff); TechEntry.addValue(builder, tv); techOffsets.push(TechEntry.endTechEntry(builder)); } const techVec = BattleReportGroup.createTechVector(builder, techOffsets); const raceStrOff = builder.createString(ship.race); const classNameOff = builder.createString(ship.className); const loadTypeOff = builder.createString(ship.loadType ?? ""); BattleReportGroup.startBattleReportGroup(builder); BattleReportGroup.addInBattle(builder, ship.inBattle ?? true); BattleReportGroup.addNumber(builder, BigInt(ship.num)); BattleReportGroup.addNumberLeft(builder, BigInt(ship.numLeft)); BattleReportGroup.addLoadQuantity(builder, ship.loadQuantity ?? 0); BattleReportGroup.addTech(builder, techVec); BattleReportGroup.addRace(builder, raceStrOff); BattleReportGroup.addClassName(builder, classNameOff); BattleReportGroup.addLoadType(builder, loadTypeOff); const groupOff = BattleReportGroup.endBattleReportGroup(builder); ShipEntry.startShipEntry(builder); ShipEntry.addKey(builder, BigInt(ship.key)); ShipEntry.addValue(builder, groupOff); shipOffsets.push(ShipEntry.endShipEntry(builder)); } const shipsVec = BattleReport.createShipsVector(builder, shipOffsets); const protocolOffsets: number[] = []; for (const action of fixture.protocol) { BattleActionReport.startBattleActionReport(builder); BattleActionReport.addAttacker(builder, BigInt(action.a)); BattleActionReport.addAttackerShipClass(builder, BigInt(action.sa)); BattleActionReport.addDefender(builder, BigInt(action.d)); BattleActionReport.addDefenderShipClass(builder, BigInt(action.sd)); BattleActionReport.addDestroyed(builder, action.x); protocolOffsets.push(BattleActionReport.endBattleActionReport(builder)); } const protocolVec = BattleReport.createProtocolVector( builder, protocolOffsets, ); const [idHi, idLo] = uuidToHiLo(fixture.id); BattleReport.startBattleReport(builder); BattleReport.addId(builder, UUID.createUUID(builder, idHi, idLo)); BattleReport.addPlanet(builder, BigInt(fixture.planet)); BattleReport.addPlanetName(builder, planetNameOff); BattleReport.addRaces(builder, racesVec); BattleReport.addShips(builder, shipsVec); BattleReport.addProtocol(builder, protocolVec); builder.finish(BattleReport.endBattleReport(builder)); return builder.asUint8Array(); } function uuidToHiLo(value: string): [bigint, bigint] { const hex = value.replace(/-/g, "").toLowerCase(); if (hex.length !== 32 || /[^0-9a-f]/.test(hex)) { throw new Error(`buildBattlePayload: invalid uuid ${value}`); } const hi = BigInt(`0x${hex.slice(0, 16)}`); const lo = BigInt(`0x${hex.slice(16, 32)}`); return [hi, lo]; }