// Phase 11 helpers for forging FlatBuffers report payloads in e2e // tests. Mirrors the engine's `report.Report` shape so the mocked // gateway can return realistic data without standing up the real // engine container. // // Phase 11 only renders planets, so the helpers keep the report shape // minimal (turn / dimensions / planet vectors). Phase 13 extended the // fixture with the optional rich planet fields (size, resources, // stockpiles, population, industry, colonists, production, free // industry) so the inspector e2e can drive the read-only display // against realistic values. Phase 15 adds a minimal `LocalShipClass` // projection so the planet inspector's Build-Ship sub-picker has data // in e2e specs (`name` only — Phase 17 widens this when ship-class // CRUD lands). Phase 21 adds a `LocalScience` projection so the // sciences table and the planet production picker's Research sub-row // have data in e2e specs. import { Builder } from "flatbuffers"; import { UUID } from "../../../src/proto/galaxy/fbs/common"; import { BattleSummary, Bombing, LocalPlanet, OtherPlanet, OtherScience, OthersShipClass, Player, Report, Route, RouteEntry, Science, ShipClass, ShipProduction, UnidentifiedPlanet, UninhabitedPlanet, } from "../../../src/proto/galaxy/fbs/report"; export interface PlanetFixture { number: number; name: string; x: number; y: number; size?: number; resources?: number; capital?: number; material?: number; } export interface InhabitedFixture extends PlanetFixture { population?: number; colonists?: number; industry?: number; production?: string; freeIndustry?: number; } export interface OtherPlanetFixture extends InhabitedFixture { owner: string; } export interface ShipClassFixture { name: string; drive?: number; armament?: number; weapons?: number; shields?: number; cargo?: number; } export interface ScienceFixture { name: string; drive?: number; weapons?: number; shields?: number; cargo?: number; } export interface PlayerFixture { name: string; drive?: number; weapons?: number; shields?: number; cargo?: number; population?: number; industry?: number; planets?: number; relation?: "WAR" | "PEACE" | "-"; votes?: number; extinct?: boolean; } export interface RouteEntryFixture { loadType: "COL" | "CAP" | "MAT" | "EMP"; destinationPlanetNumber: number; } export interface RouteFixture { sourcePlanetNumber: number; entries: RouteEntryFixture[]; } export interface OtherScienceFixture extends ScienceFixture { race: string; } export interface OtherShipClassFixture extends ShipClassFixture { race: string; mass?: number; } export interface BattleSummaryFixture { id: string; planet: number; shots: number; } export interface BombingFixture { planetNumber: number; planet: string; owner: string; attacker: string; production?: string; industry?: number; population?: number; colonists?: number; capital?: number; material?: number; attackPower?: number; wiped?: boolean; } export interface ShipProductionFixture { planet: number; class: string; cost?: number; prodUsed?: number; percent?: number; free?: number; } export interface ReportFixture { turn: number; mapWidth?: number; mapHeight?: number; localPlanets?: InhabitedFixture[]; otherPlanets?: OtherPlanetFixture[]; uninhabitedPlanets?: PlanetFixture[]; unidentifiedPlanets?: { number: number; x: number; y: number }[]; localShipClass?: ShipClassFixture[]; localScience?: ScienceFixture[]; race?: string; players?: PlayerFixture[]; routes?: RouteFixture[]; myVotes?: number; myVoteFor?: string; otherScience?: OtherScienceFixture[]; otherShipClass?: OtherShipClassFixture[]; battles?: BattleSummaryFixture[]; bombings?: BombingFixture[]; shipProductions?: ShipProductionFixture[]; } export function buildReportPayload(fixture: ReportFixture): Uint8Array { const builder = new Builder(512); const localOffsets = (fixture.localPlanets ?? []).map((planet) => { const name = builder.createString(planet.name); const production = planet.production !== undefined ? builder.createString(planet.production) : null; LocalPlanet.startLocalPlanet(builder); LocalPlanet.addNumber(builder, BigInt(planet.number)); LocalPlanet.addX(builder, planet.x); LocalPlanet.addY(builder, planet.y); LocalPlanet.addName(builder, name); LocalPlanet.addSize(builder, planet.size ?? 10); LocalPlanet.addResources(builder, planet.resources ?? 0.5); LocalPlanet.addCapital(builder, planet.capital ?? 0); LocalPlanet.addMaterial(builder, planet.material ?? 0); LocalPlanet.addPopulation(builder, planet.population ?? 0); LocalPlanet.addIndustry(builder, planet.industry ?? 0); LocalPlanet.addColonists(builder, planet.colonists ?? 0); if (production !== null) LocalPlanet.addProduction(builder, production); LocalPlanet.addFreeIndustry(builder, planet.freeIndustry ?? 0); return LocalPlanet.endLocalPlanet(builder); }); const otherOffsets = (fixture.otherPlanets ?? []).map((planet) => { const name = builder.createString(planet.name); const owner = builder.createString(planet.owner); const production = planet.production !== undefined ? builder.createString(planet.production) : null; OtherPlanet.startOtherPlanet(builder); OtherPlanet.addNumber(builder, BigInt(planet.number)); OtherPlanet.addX(builder, planet.x); OtherPlanet.addY(builder, planet.y); OtherPlanet.addName(builder, name); OtherPlanet.addOwner(builder, owner); OtherPlanet.addSize(builder, planet.size ?? 9); OtherPlanet.addResources(builder, planet.resources ?? 0.5); OtherPlanet.addCapital(builder, planet.capital ?? 0); OtherPlanet.addMaterial(builder, planet.material ?? 0); OtherPlanet.addPopulation(builder, planet.population ?? 0); OtherPlanet.addIndustry(builder, planet.industry ?? 0); OtherPlanet.addColonists(builder, planet.colonists ?? 0); if (production !== null) OtherPlanet.addProduction(builder, production); OtherPlanet.addFreeIndustry(builder, planet.freeIndustry ?? 0); return OtherPlanet.endOtherPlanet(builder); }); const uninhabitedOffsets = (fixture.uninhabitedPlanets ?? []).map( (planet) => { const name = builder.createString(planet.name); UninhabitedPlanet.startUninhabitedPlanet(builder); UninhabitedPlanet.addNumber(builder, BigInt(planet.number)); UninhabitedPlanet.addX(builder, planet.x); UninhabitedPlanet.addY(builder, planet.y); UninhabitedPlanet.addName(builder, name); UninhabitedPlanet.addSize(builder, planet.size ?? 6); UninhabitedPlanet.addResources(builder, planet.resources ?? 0.5); UninhabitedPlanet.addCapital(builder, planet.capital ?? 0); UninhabitedPlanet.addMaterial(builder, planet.material ?? 0); return UninhabitedPlanet.endUninhabitedPlanet(builder); }, ); const unidentifiedOffsets = (fixture.unidentifiedPlanets ?? []).map( (planet) => { UnidentifiedPlanet.startUnidentifiedPlanet(builder); UnidentifiedPlanet.addNumber(builder, BigInt(planet.number)); UnidentifiedPlanet.addX(builder, planet.x); UnidentifiedPlanet.addY(builder, planet.y); return UnidentifiedPlanet.endUnidentifiedPlanet(builder); }, ); const localShipClassOffsets = (fixture.localShipClass ?? []).map((cls) => { const name = builder.createString(cls.name); ShipClass.startShipClass(builder); ShipClass.addName(builder, name); ShipClass.addDrive(builder, cls.drive ?? 0); ShipClass.addArmament(builder, BigInt(cls.armament ?? 0)); ShipClass.addWeapons(builder, cls.weapons ?? 0); ShipClass.addShields(builder, cls.shields ?? 0); ShipClass.addCargo(builder, cls.cargo ?? 0); return ShipClass.endShipClass(builder); }); const localScienceOffsets = (fixture.localScience ?? []).map((sci) => { const name = builder.createString(sci.name); Science.startScience(builder); Science.addName(builder, name); Science.addDrive(builder, sci.drive ?? 0); Science.addWeapons(builder, sci.weapons ?? 0); Science.addShields(builder, sci.shields ?? 0); Science.addCargo(builder, sci.cargo ?? 0); return Science.endScience(builder); }); const playerOffsets = (fixture.players ?? []).map((p) => { const name = builder.createString(p.name); const relation = p.relation === undefined ? null : builder.createString(p.relation); Player.startPlayer(builder); Player.addName(builder, name); Player.addDrive(builder, p.drive ?? 1); Player.addWeapons(builder, p.weapons ?? 0); Player.addShields(builder, p.shields ?? 0); Player.addCargo(builder, p.cargo ?? 0); Player.addPopulation(builder, p.population ?? 0); Player.addIndustry(builder, p.industry ?? 0); Player.addPlanets(builder, p.planets ?? 0); if (relation !== null) Player.addRelation(builder, relation); Player.addVotes(builder, p.votes ?? 0); Player.addExtinct(builder, p.extinct ?? false); return Player.endPlayer(builder); }); const routeOffsets = (fixture.routes ?? []).map((route) => { const entryOffsets = route.entries.map((entry) => { const valueOffset = builder.createString(entry.loadType); RouteEntry.startRouteEntry(builder); RouteEntry.addKey(builder, BigInt(entry.destinationPlanetNumber)); RouteEntry.addValue(builder, valueOffset); return RouteEntry.endRouteEntry(builder); }); const entriesVec = Route.createRouteVector(builder, entryOffsets); Route.startRoute(builder); Route.addPlanet(builder, BigInt(route.sourcePlanetNumber)); Route.addRoute(builder, entriesVec); return Route.endRoute(builder); }); const otherScienceOffsets = (fixture.otherScience ?? []).map((sci) => { const race = builder.createString(sci.race); const name = builder.createString(sci.name); OtherScience.startOtherScience(builder); OtherScience.addRace(builder, race); OtherScience.addName(builder, name); OtherScience.addDrive(builder, sci.drive ?? 0); OtherScience.addWeapons(builder, sci.weapons ?? 0); OtherScience.addShields(builder, sci.shields ?? 0); OtherScience.addCargo(builder, sci.cargo ?? 0); return OtherScience.endOtherScience(builder); }); const otherShipClassOffsets = (fixture.otherShipClass ?? []).map((cls) => { const race = builder.createString(cls.race); const name = builder.createString(cls.name); OthersShipClass.startOthersShipClass(builder); OthersShipClass.addRace(builder, race); OthersShipClass.addName(builder, name); OthersShipClass.addDrive(builder, cls.drive ?? 0); OthersShipClass.addArmament(builder, BigInt(cls.armament ?? 0)); OthersShipClass.addWeapons(builder, cls.weapons ?? 0); OthersShipClass.addShields(builder, cls.shields ?? 0); OthersShipClass.addCargo(builder, cls.cargo ?? 0); OthersShipClass.addMass(builder, cls.mass ?? 0); return OthersShipClass.endOthersShipClass(builder); }); const bombingOffsets = (fixture.bombings ?? []).map((b) => { const planet = builder.createString(b.planet); const owner = builder.createString(b.owner); const attacker = builder.createString(b.attacker); const production = builder.createString(b.production ?? ""); Bombing.startBombing(builder); Bombing.addNumber(builder, BigInt(b.planetNumber)); Bombing.addPlanet(builder, planet); Bombing.addOwner(builder, owner); Bombing.addAttacker(builder, attacker); Bombing.addProduction(builder, production); Bombing.addIndustry(builder, b.industry ?? 0); Bombing.addPopulation(builder, b.population ?? 0); Bombing.addColonists(builder, b.colonists ?? 0); Bombing.addCapital(builder, b.capital ?? 0); Bombing.addMaterial(builder, b.material ?? 0); Bombing.addAttackPower(builder, b.attackPower ?? 0); Bombing.addWiped(builder, b.wiped ?? false); return Bombing.endBombing(builder); }); const shipProductionOffsets = (fixture.shipProductions ?? []).map((sp) => { const className = builder.createString(sp.class); ShipProduction.startShipProduction(builder); ShipProduction.addPlanet(builder, BigInt(sp.planet)); ShipProduction.addClass(builder, className); ShipProduction.addCost(builder, sp.cost ?? 0); ShipProduction.addProdUsed(builder, sp.prodUsed ?? 0); ShipProduction.addPercent(builder, sp.percent ?? 0); ShipProduction.addFree(builder, sp.free ?? 0); return ShipProduction.endShipProduction(builder); }); const localVec = localOffsets.length === 0 ? null : Report.createLocalPlanetVector(builder, localOffsets); const otherVec = otherOffsets.length === 0 ? null : Report.createOtherPlanetVector(builder, otherOffsets); const uninhabitedVec = uninhabitedOffsets.length === 0 ? null : Report.createUninhabitedPlanetVector(builder, uninhabitedOffsets); const unidentifiedVec = unidentifiedOffsets.length === 0 ? null : Report.createUnidentifiedPlanetVector(builder, unidentifiedOffsets); const localShipClassVec = localShipClassOffsets.length === 0 ? null : Report.createLocalShipClassVector(builder, localShipClassOffsets); const localScienceVec = localScienceOffsets.length === 0 ? null : Report.createLocalScienceVector(builder, localScienceOffsets); const playerVec = playerOffsets.length === 0 ? null : Report.createPlayerVector(builder, playerOffsets); const routeVec = routeOffsets.length === 0 ? null : Report.createRouteVector(builder, routeOffsets); const otherScienceVec = otherScienceOffsets.length === 0 ? null : Report.createOtherScienceVector(builder, otherScienceOffsets); const otherShipClassVec = otherShipClassOffsets.length === 0 ? null : Report.createOtherShipClassVector(builder, otherShipClassOffsets); const bombingVec = bombingOffsets.length === 0 ? null : Report.createBombingVector(builder, bombingOffsets); const shipProductionVec = shipProductionOffsets.length === 0 ? null : Report.createShipProductionVector(builder, shipProductionOffsets); // Phase 27 — `battle` carries `BattleSummary` tables, each with // an inline `id:UUID` struct plus `planet` and `shots` slots. const battleVec = (() => { const summaries = fixture.battles ?? []; if (summaries.length === 0) return null; const offsets = summaries.map((s) => { const [hi, lo] = uuidToHiLo(s.id); BattleSummary.startBattleSummary(builder); BattleSummary.addId(builder, UUID.createUUID(builder, hi, lo)); BattleSummary.addPlanet(builder, BigInt(s.planet)); BattleSummary.addShots(builder, BigInt(s.shots)); return BattleSummary.endBattleSummary(builder); }); Report.startBattleVector(builder, offsets.length); for (let i = offsets.length - 1; i >= 0; i--) { builder.addOffset(offsets[i]); } return builder.endVector(); })(); const raceOffset = fixture.race === undefined ? null : builder.createString(fixture.race); const voteForOffset = fixture.myVoteFor === undefined ? null : builder.createString(fixture.myVoteFor); const totalPlanets = (fixture.localPlanets ?? []).length + (fixture.otherPlanets ?? []).length + (fixture.uninhabitedPlanets ?? []).length + (fixture.unidentifiedPlanets ?? []).length; Report.startReport(builder); Report.addTurn(builder, BigInt(fixture.turn)); Report.addWidth(builder, fixture.mapWidth ?? 4000); Report.addHeight(builder, fixture.mapHeight ?? 4000); Report.addPlanetCount(builder, totalPlanets); if (raceOffset !== null) Report.addRace(builder, raceOffset); if (fixture.myVotes !== undefined) Report.addVotes(builder, fixture.myVotes); if (voteForOffset !== null) Report.addVoteFor(builder, voteForOffset); if (playerVec !== null) Report.addPlayer(builder, playerVec); if (localVec !== null) Report.addLocalPlanet(builder, localVec); if (otherVec !== null) Report.addOtherPlanet(builder, otherVec); if (uninhabitedVec !== null) Report.addUninhabitedPlanet(builder, uninhabitedVec); if (unidentifiedVec !== null) Report.addUnidentifiedPlanet(builder, unidentifiedVec); if (localShipClassVec !== null) Report.addLocalShipClass(builder, localShipClassVec); if (localScienceVec !== null) Report.addLocalScience(builder, localScienceVec); if (routeVec !== null) Report.addRoute(builder, routeVec); if (otherScienceVec !== null) Report.addOtherScience(builder, otherScienceVec); if (otherShipClassVec !== null) Report.addOtherShipClass(builder, otherShipClassVec); if (battleVec !== null) Report.addBattle(builder, battleVec); if (bombingVec !== null) Report.addBombing(builder, bombingVec); if (shipProductionVec !== null) Report.addShipProduction(builder, shipProductionVec); const reportOff = Report.endReport(builder); builder.finish(reportOff); 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(`buildReportPayload: invalid battle uuid ${value}`); } const hi = BigInt(`0x${hex.slice(0, 16)}`); const lo = BigInt(`0x${hex.slice(16, 32)}`); return [hi, lo]; }