Gitea #43: the Comm pilot list now shows EVERY pilot -- three stacked fixes
1. Roster count used the binary's raw entry+0x29&0x40 read on our compiled objects (databinding trap) -> garbage latched pilotCount=1. Decoded: the flag is Player::NonScoringPlayerFlag (bit 14 = Entity::NextBit); both loops now use IsScoringPlayer(). Also closes the pilotIDs[1] overrun. 2. The build-once latch races async replicant arrival (rig-proven 1-then-2): rebuild on scoring-census change [T3 accommodation, demand-latch precedent]; also un-dangles departed peers. Forensic: '[score] pilot roster built: N'. 3. Name icons were a NULL stub + zeros blank authentically -> rows invisible. Wired BTPilotNameBitmap (playerBitmapIndex -> Mission small callsign raster, the radar-label chain); replaces the raw pilot+0x1e0 key. Rig-verified: Aeolus + Boreas rows with callsigns on both nodes incl. the staggered-start race; solo unregressed (1 scoring pilot). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg
This commit is contained in:
co-authored by
Claude Opus 5
parent
7494e2ec6c
commit
b7107b1020
@@ -1110,18 +1110,33 @@ void
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// @004b0600 -- build the pilot roster once, lazily, from the application's
|
||||
// "Players" group (skipping spectators flagged 0x40).
|
||||
// "Players" group, counting SCORING players only. The binary's raw check
|
||||
// `(entry+0x29 & 0x40) == 0` decoded (Gitea #43): +0x29 is byte 1 of the
|
||||
// simulationFlags LWord @+0x28, so 0x40 there == bit 14 == Entity::NextBit ==
|
||||
// Player::NonScoringPlayerFlag (PLAYER.h:392). Players START non-scoring
|
||||
// (DefaultFlags) and become scoring when their mech links its owning player
|
||||
// (mech.cpp:688 / btplayer.cpp:1210) -- so camera ships and unseated players
|
||||
// never get a roster row. Read via the named accessor, NOT the raw offset
|
||||
// (our compiled layout differs -- the raw read is the databinding trap and
|
||||
// was THE #43 bug: garbage count latched the roster at 1 row in live MP).
|
||||
//
|
||||
void
|
||||
MechControlsMapper::BuildPilotArray()
|
||||
{
|
||||
if (pilotArrayBuilt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
pilotArrayBuilt = True;
|
||||
|
||||
pilotCount = 0;
|
||||
//
|
||||
// Scoring census. The binary latches the roster on the FIRST call
|
||||
// (pilotArrayBuilt, build-once) -- correct on 1995 pod hardware where
|
||||
// every pod's entities exist before any mission tick. In the port the
|
||||
// peers' Player/PlayerLink messages arrive over async TCP, and the rig
|
||||
// proved a frame-level race (Gitea #43): the faster-loading node ticks
|
||||
// InterpretControls once BEFORE the net queue creates the replicants,
|
||||
// latching a 1-row roster forever. [T3 accommodation, precedent =
|
||||
// the demand-latch]: re-run the build whenever the SCORING census
|
||||
// changes -- late-arriving pilots get their row, and a departed peer's
|
||||
// destroyed Player leaves the roster instead of dangling (the latched
|
||||
// design kept a freed pointer for the rest of the mission).
|
||||
//
|
||||
int scoring = 0;
|
||||
EntityGroup *players =
|
||||
application->GetEntityManager()->FindGroup("Players"); // @0050f44b
|
||||
|
||||
@@ -1130,9 +1145,9 @@ void
|
||||
ChainIteratorOf<Node*> pilots(players->groupMembers); // FUN_00421414
|
||||
for (Node *entry; (entry = pilots.ReadAndNext()) != 0; )
|
||||
{
|
||||
if ((*(byte *)((int)entry + 0x29) & 0x40) == 0) // not a spectator
|
||||
if (((Player *)entry)->IsScoringPlayer()) // binary: (+0x29 & 0x40) == 0
|
||||
{
|
||||
++pilotCount;
|
||||
++scoring;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1141,10 +1156,18 @@ void
|
||||
// an 8-pod game fits). pilotCount beyond that would overrun the block in
|
||||
// the binary too -- clamp so the `<= pilotCount` zero-loop (which zeroes
|
||||
// the local slot 0 PLUS pilotCount remotes, faithful) stays in bounds.
|
||||
if (pilotCount > PilotArraySlots - 1)
|
||||
pilotCount = PilotArraySlots - 1;
|
||||
if (scoring > PilotArraySlots - 1)
|
||||
scoring = PilotArraySlots - 1;
|
||||
|
||||
pilotIDs = new int[pilotCount]; // FUN_004022b0
|
||||
if (pilotArrayBuilt && scoring == pilotCount)
|
||||
{
|
||||
return; // steady state -- the binary's built-once path
|
||||
}
|
||||
pilotArrayBuilt = True;
|
||||
pilotCount = scoring;
|
||||
|
||||
delete[] pilotIDs; // 0 on first build (ctor)
|
||||
pilotIDs = new int[pilotCount > 0 ? pilotCount : 1]; // FUN_004022b0
|
||||
|
||||
for (int i = 0; i <= pilotCount; ++i)
|
||||
{
|
||||
@@ -1153,6 +1176,10 @@ void
|
||||
|
||||
FillPilotArray(); // FUN_004b06cc
|
||||
ChooseDefaultPilot(); // FUN_004b07f0
|
||||
|
||||
// Always-on forensic (Gitea #43): one line per census change.
|
||||
DEBUG_STREAM << "[score] pilot roster built: " << pilotCount
|
||||
<< " scoring pilot(s)" << std::endl;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -1203,10 +1230,11 @@ void
|
||||
int slot = 1;
|
||||
for (Node *entry; (entry = pilots.ReadAndNext()) != 0; )
|
||||
{
|
||||
// TODO(mp-roster): the binary skips spectators via a flag byte at
|
||||
// entry+0x29 (bit 0x40) -- its WinTesla home is unmapped; solo has no
|
||||
// spectators, so the check is omitted until the flag is reconciled.
|
||||
if (entry == (Node *)local_pilot) // already slot 0
|
||||
// Binary @004b0790: skip non-scoring players (the same +0x29 & 0x40
|
||||
// == NonScoringPlayerFlag check as the count loop -- resolved, Gitea
|
||||
// #43) and the local pilot (already slot 0).
|
||||
if (!((Player *)entry)->IsScoringPlayer()
|
||||
|| entry == (Node *)local_pilot)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user