fix(headless): FA6 — name-match the Recruit bot instead of nearest-any-player
The second live gate run exposed a real environmental hazard: this shared ACE dev instance has a THIRD player character online (+Je, guid 0x50000001), and after @teleallto it ended up nearer to the Leader bot than the actual Recruit bot (+Horan, 0x5000000B). RuntimeFriendlyTargetQuery. FindClosestOtherPlayer — "nearest ANY other player" — picked +Je, and the fellowship recruit sent to it obviously never completed (confirmed live: WaitRecruited/WaitForRecruit both timed out, both bots quarantined and gracefully logged out cleanly). RuntimeFriendlyTargetQuery.FindPlayerByName resolves the nearest player whose streamed name matches exactly, with 3 new conformance tests (preferring the named player over a closer stranger, returning null when absent, and case-sensitivity/hidden/no-draw/self rejection). FellowshipAllegianceGateCoordinator (AcDream.Headless.Policies) is a small same-process, no-locking (single update thread) carrier for the Recruit bot's own discovered character name — set by its own HeadlessSessionHost the instant CharacterList selection resolves it, which IS D8's "discover it live" mechanism, not a hard-coded value. Constructed once per HeadlessProcessHost and threaded through HeadlessBotPolicyFactory.Create into the Leader policy, which now name-matches instead of taking whichever player entity happens to be closest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
11641597db
commit
ab79b91f1b
5 changed files with 280 additions and 33 deletions
|
|
@ -141,6 +141,106 @@ public sealed class RuntimeFriendlyTargetQueryTests
|
|||
Assert.Null(RuntimeFriendlyTargetQuery.TryGetName(runtime, 0x50000099u));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FA6 second gate attempt (live-run finding): a shared ACE dev
|
||||
/// instance can have a THIRD player online and NEARER than the actual
|
||||
/// expected counterpart — <see cref="RuntimeFriendlyTargetQuery.FindClosestOtherPlayer"/>
|
||||
/// picked the wrong one live. <see cref="RuntimeFriendlyTargetQuery.FindPlayerByName"/>
|
||||
/// must reject the closer stranger and resolve the named target even
|
||||
/// though it is farther away.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FindPlayerByName_PrefersTheNamedPlayerOverACloserStranger()
|
||||
{
|
||||
using GameRuntime runtime = Create();
|
||||
runtime.PlayerIdentity.ServerGuid = Player;
|
||||
Add(runtime, Player, 0x01010001u, 10f, 10f, "Self", PlayerBit);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000010u,
|
||||
0x01010001u,
|
||||
11f,
|
||||
10f,
|
||||
"+Je",
|
||||
PlayerBit);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000011u,
|
||||
0x01010001u,
|
||||
30f,
|
||||
10f,
|
||||
"+Horan",
|
||||
PlayerBit);
|
||||
|
||||
Assert.Equal(
|
||||
0x50000011u,
|
||||
RuntimeFriendlyTargetQuery.FindPlayerByName(runtime, "+Horan"));
|
||||
// The plain nearest-any-player query still (correctly) prefers the
|
||||
// closer stranger — this test pins that FindPlayerByName diverges
|
||||
// from it deliberately, not that the sibling method is "buggy".
|
||||
Assert.Equal(
|
||||
0x50000010u,
|
||||
RuntimeFriendlyTargetQuery.FindClosestOtherPlayer(runtime));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindPlayerByName_ReturnsNullWhenNoPlayerHasThatName()
|
||||
{
|
||||
using GameRuntime runtime = Create();
|
||||
runtime.PlayerIdentity.ServerGuid = Player;
|
||||
Add(runtime, Player, 0x01010001u, 10f, 10f, "Self", PlayerBit);
|
||||
Add(runtime, 0x50000010u, 0x01010001u, 11f, 10f, "+Je", PlayerBit);
|
||||
|
||||
Assert.Null(
|
||||
RuntimeFriendlyTargetQuery.FindPlayerByName(runtime, "+Horan"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindPlayerByName_IsCaseSensitiveAndSkipsHiddenNoDrawAndSelf()
|
||||
{
|
||||
using GameRuntime runtime = Create();
|
||||
runtime.PlayerIdentity.ServerGuid = Player;
|
||||
Add(runtime, Player, 0x01010001u, 10f, 10f, "+Horan", PlayerBit);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000010u,
|
||||
0x01010001u,
|
||||
11f,
|
||||
10f,
|
||||
"+horan",
|
||||
PlayerBit);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000011u,
|
||||
0x01010001u,
|
||||
12f,
|
||||
10f,
|
||||
"+Horan",
|
||||
PlayerBit,
|
||||
PhysicsStateFlags.Hidden);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000012u,
|
||||
0x01010001u,
|
||||
13f,
|
||||
10f,
|
||||
"+Horan",
|
||||
PlayerBit,
|
||||
PhysicsStateFlags.NoDraw);
|
||||
Add(
|
||||
runtime,
|
||||
0x50000013u,
|
||||
0x01010001u,
|
||||
14f,
|
||||
10f,
|
||||
"+Horan",
|
||||
PlayerBit);
|
||||
|
||||
Assert.Equal(
|
||||
0x50000013u,
|
||||
RuntimeFriendlyTargetQuery.FindPlayerByName(runtime, "+Horan"));
|
||||
}
|
||||
|
||||
private static GameRuntime Create()
|
||||
{
|
||||
var operations = new Operations();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue