feat(model+ui): F8-05 — race on OtherGroup, real attribution + N×M label
Tests · UI / test (push) Has been cancelled
Tests · Go / test (pull_request) Successful in 2m6s
Tests · Go / test (push) Successful in 2m6s
Tests · Integration / integration (pull_request) Successful in 1m51s
Tests · UI / test (pull_request) Successful in 3m53s

Issue #48 п.32 ("Stationed ship groups") shipped with a fragile race
fallback: when a foreign group sat on a non-`other`-kind planet the
inspector printed a generic "foreign" label, which collapsed the
race dropdown to a single uninformative bucket. The engine FBS
contract did not carry per-group race either, so live games hit the
same gap. This patch carries race authoritatively from the engine
through every layer down to the inspector.

Wire format & engine
- `pkg/schema/fbs/report.fbs`: add `race:string` to `OtherGroup` and
  `LocalGroup` (additive — old clients ignore).
- `pkg/schema/fbs/report/`: regenerated Go bindings.
- `ui/frontend/src/proto/galaxy/fbs/report/`: regenerated TS bindings.
- `pkg/model/report.OtherGroup.Race`: new field; carried through
  `LocalGroup` via the embedded `OtherGroup`.
- `pkg/transcoder/report.go`: encode + decode `race` on both
  `LocalGroup` and `OtherGroup`.
- `game/internal/controller/report.go.otherGroup`: set `v.Race`
  from `c.g.Race[c.RaceIndex(sg.OwnerID)].Name` so every emitted
  group — own or foreign — carries the resolved race name.

Legacy parser
- `tools/local-dev/legacy-report/parser.go`: capture the
  `<Race> Groups` header into `pendingOtherGroup.race`, fill local
  group `Race` from `p.rep.Race`, propagate both into the
  `report.OtherGroup` rows.
- Tests + smoke counts updated; regenerated `KNNTS{039,041}.json`
  fixtures so the synthetic loader carries the new field.

UI
- `ui/frontend/src/api/`: `ReportShipGroupBase.race` field;
  synthetic loader + FBS decoder populate it.
- `ui/frontend/src/lib/inspectors/planet/ship-groups.svelte`: the
  stationed-groups inspector picks race directly from
  `group.race` (own falls back to `localRace`, both finally to the
  `race.unknown` placeholder). The planet-owner / "foreign"
  heuristic is gone.
- Row label changes from "N ships mass M" to a compact
  `<class>` | `<N ×>` | `<mass>` three-column layout: the count
  cell is right-aligned tabular, the mass cell is right-aligned
  monospace + tabular, matching the inspector / calculator number
  conventions. Stale i18n keys removed
  (`ship_groups.row.count`, `.row.mass`, `.race.foreign`).
- All affected unit tests (8 files) carry the new `race` field.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-27 16:23:17 +02:00
parent cc4bc3c2b7
commit 24c68e9846
26 changed files with 3601 additions and 1544 deletions
+1
View File
@@ -733,6 +733,7 @@ func (c *Cache) otherGroup(v *mr.OtherGroup, sg *game.ShipGroup, st *game.ShipTy
}
v.Speed = mr.F(sg.Speed(st))
v.Mass = mr.F(st.EmptyMass())
v.Race = c.g.Race[c.RaceIndex(sg.OwnerID)].Name
}
func (c *Cache) localPlanet(v *mr.LocalPlanet, p *game.Planet) {
+7
View File
@@ -52,6 +52,13 @@ type OtherGroup struct {
Range *Float `json:"range,omitempty"`
Speed Float `json:"speed"`
Mass Float `json:"mass"`
// Race is the owner's display name resolved from `sg.OwnerID`
// (or, for legacy reports, the section header that introduced
// the row). The local race fills this in for its own
// `LocalGroup` copies via the embedded `OtherGroup` field so
// every group carries an authoritative attribution rather than a
// "foreign" fallback heuristic at render time.
Race string `json:"race,omitempty"`
}
type UnidentifiedGroup struct {
+2
View File
@@ -168,6 +168,7 @@ table OtherGroup {
range:float32 = null;
speed:float32;
mass:float32;
race:string;
}
table LocalGroup {
@@ -184,6 +185,7 @@ table LocalGroup {
id:common.UUID (required);
state:string;
fleet:string;
race:string;
}
table LocalFleet {
+12 -1
View File
@@ -194,8 +194,16 @@ func (rcv *LocalGroup) Fleet() []byte {
return nil
}
func (rcv *LocalGroup) Race() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(30))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func LocalGroupStart(builder *flatbuffers.Builder) {
builder.StartObject(13)
builder.StartObject(14)
}
func LocalGroupAddNumber(builder *flatbuffers.Builder, number uint64) {
builder.PrependUint64Slot(0, number, 0)
@@ -241,6 +249,9 @@ func LocalGroupAddState(builder *flatbuffers.Builder, state flatbuffers.UOffsetT
func LocalGroupAddFleet(builder *flatbuffers.Builder, fleet flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(12, flatbuffers.UOffsetT(fleet), 0)
}
func LocalGroupAddRace(builder *flatbuffers.Builder, race flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(13, flatbuffers.UOffsetT(race), 0)
}
func LocalGroupEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject()
}
+12 -1
View File
@@ -163,8 +163,16 @@ func (rcv *OtherGroup) MutateMass(n float32) bool {
return rcv._tab.MutateFloat32Slot(22, n)
}
func (rcv *OtherGroup) Race() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(24))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func OtherGroupStart(builder *flatbuffers.Builder) {
builder.StartObject(10)
builder.StartObject(11)
}
func OtherGroupAddNumber(builder *flatbuffers.Builder, number uint64) {
builder.PrependUint64Slot(0, number, 0)
@@ -201,6 +209,9 @@ func OtherGroupAddSpeed(builder *flatbuffers.Builder, speed float32) {
func OtherGroupAddMass(builder *flatbuffers.Builder, mass float32) {
builder.PrependFloat32Slot(9, mass, 0.0)
}
func OtherGroupAddRace(builder *flatbuffers.Builder, race flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(10, flatbuffers.UOffsetT(race), 0)
}
func OtherGroupEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject()
}
+6
View File
@@ -513,6 +513,7 @@ func encodeReportLocalGroup(builder *flatbuffers.Builder, group *model.LocalGrou
class := builder.CreateString(group.Class)
cargo := builder.CreateString(group.Cargo)
state := builder.CreateString(group.State)
race := builder.CreateString(group.Race)
tech := encodeReportTechEntryVector(builder, group.Tech)
var fleet flatbuffers.UOffsetT
@@ -544,6 +545,7 @@ func encodeReportLocalGroup(builder *flatbuffers.Builder, group *model.LocalGrou
if group.Fleet != nil {
fbs.LocalGroupAddFleet(builder, fleet)
}
fbs.LocalGroupAddRace(builder, race)
return fbs.LocalGroupEnd(builder)
}
@@ -551,6 +553,7 @@ func encodeReportOtherGroup(builder *flatbuffers.Builder, group *model.OtherGrou
class := builder.CreateString(group.Class)
cargo := builder.CreateString(group.Cargo)
tech := encodeReportTechEntryVector(builder, group.Tech)
race := builder.CreateString(group.Race)
fbs.OtherGroupStart(builder)
fbs.OtherGroupAddNumber(builder, uint64(group.Number))
@@ -569,6 +572,7 @@ func encodeReportOtherGroup(builder *flatbuffers.Builder, group *model.OtherGrou
}
fbs.OtherGroupAddSpeed(builder, reportFloatToFBS(group.Speed))
fbs.OtherGroupAddMass(builder, reportFloatToFBS(group.Mass))
fbs.OtherGroupAddRace(builder, race)
return fbs.OtherGroupEnd(builder)
}
@@ -1134,6 +1138,7 @@ func decodeReportLocalGroupVector(flatReport *fbs.Report, result *model.Report)
Destination: destination,
Speed: reportFloatFromFBS(item.Speed()),
Mass: reportFloatFromFBS(item.Mass()),
Race: string(item.Race()),
},
ID: uuidFromHiLo(id.Hi(), id.Lo()),
State: string(item.State()),
@@ -1196,6 +1201,7 @@ func decodeReportOtherGroupVector(flatReport *fbs.Report, result *model.Report)
Destination: destination,
Speed: reportFloatFromFBS(item.Speed()),
Mass: reportFloatFromFBS(item.Mass()),
Race: string(item.Race()),
}
if origin := item.Origin(); origin != nil {
+2 -1
View File
@@ -352,6 +352,7 @@ func sampleReport() *model.Report {
Range: &rangeB,
Speed: model.Float(2.5),
Mass: model.Float(12.0),
Race: "Earthlings",
},
ID: uuid.MustParse("33333333-3333-3333-3333-333333333333"),
State: "in_orbit",
@@ -359,7 +360,7 @@ func sampleReport() *model.Report {
},
},
OtherGroup: []model.OtherGroup{
{Number: 2, Class: "scout", Tech: map[string]model.Float{"CARGO": model.Float(1.25), "DRIVE": model.Float(1.75)}, Cargo: "CAP", Load: model.Float(3.5), Destination: 5, Speed: model.Float(2.25), Mass: model.Float(8.5)},
{Number: 2, Class: "scout", Tech: map[string]model.Float{"CARGO": model.Float(1.25), "DRIVE": model.Float(1.75)}, Cargo: "CAP", Load: model.Float(3.5), Destination: 5, Speed: model.Float(2.25), Mass: model.Float(8.5), Race: "Klingons"},
},
UnidentifiedGroup: []model.UnidentifiedGroup{
{X: model.Float(10.0), Y: model.Float(11.0)},
+8 -1
View File
@@ -158,8 +158,12 @@ type pendingGroup struct {
// foreign planets, so the destination resolves against the parsed
// planet tables in [parser.resolvePending]. The legacy row carries
// no origin / range / fleet columns, so foreign groups are always
// treated as stationed at the destination.
// treated as stationed at the destination. The `race` field is set
// from the section header that introduced the row, so the inspector
// can name the foreign owner directly without falling back to a
// generic "foreign" placeholder.
type pendingOtherGroup struct {
race string
count uint
class string
drive float64
@@ -1122,6 +1126,7 @@ func (p *parser) parseOtherGroup(fields []string) {
mass, _ := parseFloat(fields[10])
p.pendingOtherGroups = append(p.pendingOtherGroups, pendingOtherGroup{
race: p.otherOwner,
count: uint(count),
class: fields[1],
drive: drive,
@@ -1230,6 +1235,7 @@ func (p *parser) resolvePending() {
Origin: origin,
Range: rng,
Mass: report.F(pg.mass),
Race: p.rep.Race,
},
ID: syntheticGroupID(pg.g),
State: pg.state,
@@ -1256,6 +1262,7 @@ func (p *parser) resolvePending() {
Load: report.F(pg.load),
Destination: dest,
Mass: report.F(pg.mass),
Race: pg.race,
})
}
@@ -844,6 +844,9 @@ func TestParseOtherAndUnidentifiedGroups(t *testing.T) {
t.Errorf("a (Class, Number, Destination) = (%q, %d, %d), want (\"Skiff\", 6, 17)",
a.Class, a.Number, a.Destination)
}
if a.Race != "Aliens" {
t.Errorf("a Race = %q, want %q (carried over from the section header)", a.Race, "Aliens")
}
if a.Cargo != "COL" || float64(a.Load) != 2 || float64(a.Mass) != 3 {
t.Errorf("a (Cargo, Load, Mass) = (%q, %v, %v), want (\"COL\", 2, 3)",
a.Cargo, float64(a.Load), float64(a.Mass))
@@ -860,6 +863,9 @@ func TestParseOtherAndUnidentifiedGroups(t *testing.T) {
t.Errorf("b (Class, Number, Destination) = (%q, %d, %d), want (\"Phantom\", 1, 42)",
b.Class, b.Number, b.Destination)
}
if b.Race != "Reds" {
t.Errorf("b Race = %q, want %q (separate section, separate owner)", b.Race, "Reds")
}
if got, want := len(rep.UnidentifiedGroup), 2; got != want {
t.Fatalf("len(UnidentifiedGroup) = %d, want %d", got, want)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+11
View File
@@ -193,6 +193,15 @@ export interface ReportShipGroupBase {
range: number | null;
speed: number;
mass: number;
/**
* Owning race for the group. The engine fills this from
* `sg.OwnerID` (and the legacy parser from the section header
* that introduced the row) so the inspector can name foreign
* owners directly instead of falling back to a "foreign"
* placeholder when the planet kind does not carry an `owner`
* heuristic. Empty when the source report predates the field.
*/
race: string;
}
/**
@@ -869,6 +878,7 @@ function decodeLocalShipGroups(report: Report): ReportLocalShipGroup[] {
mass: g.mass(),
state: g.state() ?? "",
fleet: g.fleet(),
race: g.race() ?? "",
});
}
return out;
@@ -895,6 +905,7 @@ function decodeOtherShipGroups(report: Report): ReportOtherShipGroup[] {
range,
speed: g.speed(),
mass: g.mass(),
race: g.race() ?? "",
});
}
return out;
+3
View File
@@ -186,6 +186,7 @@ interface SyntheticShipGroup {
mass?: number;
state?: string;
fleet?: string;
race?: string;
}
interface SyntheticIncomingGroup {
@@ -344,6 +345,7 @@ function decodeSyntheticReport(json: unknown): GameReport {
mass: numOr0(g.mass),
state: typeof g.state === "string" ? g.state : "",
fleet: typeof g.fleet === "string" ? g.fleet : null,
race: typeof g.race === "string" ? g.race : race,
}),
);
const otherShipGroups: ReportOtherShipGroup[] = (root.otherGroup ?? []).map(
@@ -358,6 +360,7 @@ function decodeSyntheticReport(json: unknown): GameReport {
range: typeof g.range === "number" ? g.range : null,
speed: numOr0(g.speed),
mass: numOr0(g.mass),
race: typeof g.race === "string" ? g.race : "",
}),
);
const incomingShipGroups: ReportIncomingShipGroup[] = (
-3
View File
@@ -600,10 +600,7 @@ const en = {
"game.inspector.planet.ship_groups.race_filter.aria": "stationed race",
"game.inspector.planet.ship_groups.title": "stationed ship groups",
"game.inspector.planet.ship_groups.row.count": "{count} ships",
"game.inspector.planet.ship_groups.row.mass": "mass {mass}",
"game.inspector.planet.ship_groups.race.unknown": "unknown",
"game.inspector.planet.ship_groups.race.foreign": "foreign",
"game.report.loading": "loading report…",
"game.report.back_to_map": "back to map",
-3
View File
@@ -601,10 +601,7 @@ const ru: Record<keyof typeof en, string> = {
"game.inspector.planet.ship_groups.race_filter.aria": "раса в орбите",
"game.inspector.planet.ship_groups.title": "корабли на орбите",
"game.inspector.planet.ship_groups.row.count": "{count} кораблей",
"game.inspector.planet.ship_groups.row.mass": "масса {mass}",
"game.inspector.planet.ship_groups.race.unknown": "неизвестно",
"game.inspector.planet.ship_groups.race.foreign": "чужие",
"game.report.loading": "загрузка отчёта…",
"game.report.back_to_map": "назад к карте",
@@ -66,6 +66,17 @@ modes because the dropdown already names the active race.
groupId: string | null;
}
// F8-05 owner-feedback: the row carries the authoritative `race`
// field projected by the engine (and by the legacy parser via the
// `<Race> Groups` header), so every stationed group surfaces its
// real owner. The planet-owner / "foreign" fallback is gone — when
// the wire carries no race the row falls back to the i18n
// `race.unknown` placeholder, matching how the local-race column
// degrades if `localRace` is missing.
const unknownRace = $derived(
i18n.t("game.inspector.planet.ship_groups.race.unknown"),
);
const stationedRows: StationedRow[] = $derived.by(() => {
const rows: StationedRow[] = [];
for (const g of localShipGroups) {
@@ -73,7 +84,7 @@ modes because the dropdown already names the active race.
if (g.origin !== null || g.range !== null) continue;
rows.push({
key: `local:${g.id}`,
race: localRace || i18n.t("game.inspector.planet.ship_groups.race.unknown"),
race: g.race || localRace || unknownRace,
class: g.class,
count: g.count,
mass: g.mass,
@@ -81,16 +92,13 @@ modes because the dropdown already names the active race.
groupId: g.id,
});
}
const foreignRace =
planet.owner ??
i18n.t("game.inspector.planet.ship_groups.race.foreign");
for (let i = 0; i < otherShipGroups.length; i++) {
const g = otherShipGroups[i]!;
if (g.destination !== planet.number) continue;
if (g.origin !== null || g.range !== null) continue;
rows.push({
key: `other:${i}`,
race: foreignRace,
race: g.race || unknownRace,
class: g.class,
count: g.count,
mass: g.mass,
@@ -183,34 +191,20 @@ modes because the dropdown already names the active race.
{@const groupId = row.groupId}
<button
type="button"
class="select"
class="cells select"
data-testid="inspector-planet-ship-groups-select"
onclick={() => selectLocalGroup(groupId)}
>
<span class="class">{row.class}</span>
<span class="count">
{i18n.t("game.inspector.planet.ship_groups.row.count", {
count: String(row.count),
})}
</span>
<span class="mass">
{i18n.t("game.inspector.planet.ship_groups.row.mass", {
mass: formatFloat(row.mass),
})}
</span>
<span class="count">{row.count} ×</span>
<span class="mass">{formatFloat(row.mass)}</span>
</button>
{:else}
<div class="cells">
<span class="class">{row.class}</span>
<span class="count">
{i18n.t("game.inspector.planet.ship_groups.row.count", {
count: String(row.count),
})}
</span>
<span class="mass">
{i18n.t("game.inspector.planet.ship_groups.row.mass", {
mass: formatFloat(row.mass),
})}
</span>
<span class="count">{row.count} ×</span>
<span class="mass">{formatFloat(row.mass)}</span>
</div>
{/if}
</li>
{/each}
@@ -260,13 +254,12 @@ modes because the dropdown already names the active race.
.row {
display: block;
font-size: 0.85rem;
font-variant-numeric: tabular-nums;
}
.row > span,
.row > .select {
.cells {
display: grid;
grid-template-columns: 1fr auto auto;
gap: 0.5rem;
align-items: baseline;
}
.select {
width: 100%;
@@ -286,8 +279,15 @@ modes because the dropdown already names the active race.
.class {
color: var(--color-text-muted);
}
.count,
.count {
color: var(--color-text-muted);
text-align: right;
font-variant-numeric: tabular-nums;
}
.mass {
color: var(--color-text-muted);
text-align: right;
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
}
</style>
@@ -104,8 +104,15 @@ fleet(optionalEncoding?:any):string|Uint8Array|null {
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
race():string|null
race(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
race(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 30);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startLocalGroup(builder:flatbuffers.Builder) {
builder.startObject(13);
builder.startObject(14);
}
static addNumber(builder:flatbuffers.Builder, number:bigint) {
@@ -172,6 +179,10 @@ static addFleet(builder:flatbuffers.Builder, fleetOffset:flatbuffers.Offset) {
builder.addFieldOffset(12, fleetOffset, 0);
}
static addRace(builder:flatbuffers.Builder, raceOffset:flatbuffers.Offset) {
builder.addFieldOffset(13, raceOffset, 0);
}
static endLocalGroup(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
builder.requiredField(offset, 24) // id
@@ -193,7 +204,8 @@ unpack(): LocalGroupT {
this.mass(),
(this.id() !== null ? this.id()!.unpack() : null),
this.state(),
this.fleet()
this.fleet(),
this.race()
);
}
@@ -212,6 +224,7 @@ unpackTo(_o: LocalGroupT): void {
_o.id = (this.id() !== null ? this.id()!.unpack() : null);
_o.state = this.state();
_o.fleet = this.fleet();
_o.race = this.race();
}
}
@@ -229,7 +242,8 @@ constructor(
public mass: number = 0.0,
public id: UUIDT|null = null,
public state: string|Uint8Array|null = null,
public fleet: string|Uint8Array|null = null
public fleet: string|Uint8Array|null = null,
public race: string|Uint8Array|null = null
){}
@@ -239,6 +253,7 @@ pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const cargo = (this.cargo !== null ? builder.createString(this.cargo!) : 0);
const state = (this.state !== null ? builder.createString(this.state!) : 0);
const fleet = (this.fleet !== null ? builder.createString(this.fleet!) : 0);
const race = (this.race !== null ? builder.createString(this.race!) : 0);
LocalGroup.startLocalGroup(builder);
LocalGroup.addNumber(builder, this.number);
@@ -256,6 +271,7 @@ pack(builder:flatbuffers.Builder): flatbuffers.Offset {
LocalGroup.addId(builder, (this.id !== null ? this.id!.pack(builder) : 0));
LocalGroup.addState(builder, state);
LocalGroup.addFleet(builder, fleet);
LocalGroup.addRace(builder, race);
return LocalGroup.endLocalGroup(builder);
}
@@ -84,8 +84,15 @@ mass():number {
return offset ? this.bb!.readFloat32(this.bb_pos + offset) : 0.0;
}
race():string|null
race(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null
race(optionalEncoding?:any):string|Uint8Array|null {
const offset = this.bb!.__offset(this.bb_pos, 24);
return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null;
}
static startOtherGroup(builder:flatbuffers.Builder) {
builder.startObject(10);
builder.startObject(11);
}
static addNumber(builder:flatbuffers.Builder, number:bigint) {
@@ -140,12 +147,16 @@ static addMass(builder:flatbuffers.Builder, mass:number) {
builder.addFieldFloat32(9, mass, 0.0);
}
static addRace(builder:flatbuffers.Builder, raceOffset:flatbuffers.Offset) {
builder.addFieldOffset(10, raceOffset, 0);
}
static endOtherGroup(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createOtherGroup(builder:flatbuffers.Builder, number:bigint, class_Offset:flatbuffers.Offset, techOffset:flatbuffers.Offset, cargoOffset:flatbuffers.Offset, load:number, destination:bigint, origin:bigint|null, range:number|null, speed:number, mass:number):flatbuffers.Offset {
static createOtherGroup(builder:flatbuffers.Builder, number:bigint, class_Offset:flatbuffers.Offset, techOffset:flatbuffers.Offset, cargoOffset:flatbuffers.Offset, load:number, destination:bigint, origin:bigint|null, range:number|null, speed:number, mass:number, raceOffset:flatbuffers.Offset):flatbuffers.Offset {
OtherGroup.startOtherGroup(builder);
OtherGroup.addNumber(builder, number);
OtherGroup.addClass(builder, class_Offset);
@@ -159,6 +170,7 @@ static createOtherGroup(builder:flatbuffers.Builder, number:bigint, class_Offset
OtherGroup.addRange(builder, range);
OtherGroup.addSpeed(builder, speed);
OtherGroup.addMass(builder, mass);
OtherGroup.addRace(builder, raceOffset);
return OtherGroup.endOtherGroup(builder);
}
@@ -173,7 +185,8 @@ unpack(): OtherGroupT {
this.origin(),
this.range(),
this.speed(),
this.mass()
this.mass(),
this.race()
);
}
@@ -189,6 +202,7 @@ unpackTo(_o: OtherGroupT): void {
_o.range = this.range();
_o.speed = this.speed();
_o.mass = this.mass();
_o.race = this.race();
}
}
@@ -203,7 +217,8 @@ constructor(
public origin: bigint|null = null,
public range: number|null = null,
public speed: number = 0.0,
public mass: number = 0.0
public mass: number = 0.0,
public race: string|Uint8Array|null = null
){}
@@ -211,6 +226,7 @@ pack(builder:flatbuffers.Builder): flatbuffers.Offset {
const class_ = (this.class_ !== null ? builder.createString(this.class_!) : 0);
const tech = OtherGroup.createTechVector(builder, builder.createObjectOffsetList(this.tech));
const cargo = (this.cargo !== null ? builder.createString(this.cargo!) : 0);
const race = (this.race !== null ? builder.createString(this.race!) : 0);
return OtherGroup.createOtherGroup(builder,
this.number,
@@ -222,7 +238,8 @@ pack(builder:flatbuffers.Builder): flatbuffers.Offset {
this.origin,
this.range,
this.speed,
this.mass
this.mass,
race
);
}
}
@@ -69,6 +69,7 @@ function localGroup(
mass: 12,
state: "In_Orbit",
fleet: null,
race: "Earthlings",
...overrides,
};
}
@@ -87,6 +88,7 @@ function otherGroup(
range: null,
speed: 0,
mass: 25,
race: "Klingons",
...overrides,
};
}
@@ -108,6 +108,7 @@ function localGroup(
mass: 12,
state: "In_Orbit",
fleet: null,
race: "Earthlings",
...overrides,
};
}
@@ -119,6 +119,7 @@ function group(
mass: 12,
state: "In_Orbit",
fleet: null,
race: "Earthlings",
...overrides,
};
}
@@ -104,6 +104,7 @@ function group(
mass: 25,
state: "In_Orbit",
fleet: null,
race: "Earthlings",
...overrides,
};
}
@@ -79,6 +79,7 @@ function localGroup(
mass: 12,
state: "In_Orbit",
fleet: null,
race: "Earthlings",
...overrides,
};
}
@@ -158,6 +159,7 @@ describe("ship-group inspector", () => {
range: null,
speed: 0,
mass: 50,
race: "Klingons",
};
const selection: ShipGroupSelection = { variant: "other", group };
const ui = render(ShipGroup, { props: { selection, planets: PLANETS } });
@@ -45,6 +45,7 @@ function localGroup(overrides: Partial<ReportLocalShipGroup> & Pick<ReportLocalS
mass: 1,
state: "In_Orbit",
fleet: null,
race: "Earthlings",
...overrides,
};
}
@@ -79,6 +79,7 @@ function makeLocalShipGroup(
mass: 0,
state: "InOrbit",
fleet: null,
race: "Earthlings",
...overrides,
};
}
@@ -97,6 +98,7 @@ function makeOtherShipGroup(
range: null,
speed: 1,
mass: 0,
race: "Klingons",
...overrides,
};
}
@@ -78,6 +78,7 @@ describe("reportToWorld — ship groups", () => {
mass: 12,
state: "In_Orbit",
fleet: null,
race: "Earthlings",
},
],
}),
@@ -112,6 +113,7 @@ describe("reportToWorld — ship groups", () => {
mass: 50,
state: "In_Space",
fleet: null,
race: "Earthlings",
},
],
}),
@@ -237,6 +239,7 @@ describe("reportToWorld — ship groups", () => {
origin: null,
range: null,
speed: 0,
race: "Earthlings",
mass: 1,
state: "In_Orbit",
fleet: null,