fix: complete retail parity stability pass
All checks were successful
CI / linux-portable (push) Successful in 3m41s
CI / windows-gate (push) Successful in 6m49s
CI / release (push) Successful in 3m22s

This commit is contained in:
Erik 2026-08-28 20:01:39 +02:00
parent d3df4cb20a
commit f7aa8e0eb7
131 changed files with 7765 additions and 1190 deletions

View file

@ -101,6 +101,39 @@ public sealed class LiveSessionCommandRouterTests
calls);
}
[Fact]
public void AdministrationBindings_UseTheSameActivationAndDisposalGuard()
{
var calls = new List<string>();
ClientCommandController.Bindings client = NewClientBindings() with
{
Administration = NewAdministrationBindings() with
{
SetMotd = text => calls.Add("motd:" + text),
BootSpecificHouseGuest = name => calls.Add("boot:" + name),
},
};
var router = NewRouter(clientBindings: client);
Publish();
router.Activate();
Publish();
router.Dispose();
Publish();
Assert.Equal(["motd:Welcome", "boot:Lord Bob"], calls);
void Publish()
{
router.Publish(new ExecuteClientCommandCmd(
ClientCommandId.AllegianceMotd,
"set Welcome"));
router.Publish(new ExecuteClientCommandCmd(
ClientCommandId.HouseBoot,
"Lord Bob"));
}
}
[Fact]
public void InactiveAndDisposedRouter_CannotReachTransport()
{
@ -831,6 +864,7 @@ public sealed class LiveSessionCommandRouterTests
ToggleFrameRate: () => { },
ToggleUiLock: () => { },
ShowSystemMessage: _ => { },
ShowClientLocalMessage: _ => { },
ShowWeenieError: _ => { },
PlayerPublicWeenieBitfield: () => null,
ClientVersion: () => "test",
@ -879,5 +913,49 @@ public sealed class LiveSessionCommandRouterTests
LeaveGmChannel: _ => { },
RecallAllegianceHometown: () => { },
RequestAllegianceInfo: _ => { },
AbandonHouse: () => { });
AbandonHouse: () => { },
Administration: NewAdministrationBindings(),
IsPersistentDaylight: () => false,
SetPersistentDaylight: _ => { },
SetLandscapeRadius: _ => { },
SetFieldOfView: _ => { });
private static ClientCommandController.AdministrationBindings
NewAdministrationBindings() => new(
BreakAllegianceBoot: (_, _) => { },
AllegianceChatBoot: (_, _) => { },
AllegianceChatGag: (_, _) => { },
AllegianceBroadcast: _ => { },
ListAllegianceBans: () => { },
AddAllegianceBan: _ => { },
RemoveAllegianceBan: _ => { },
ListAllegianceOfficers: () => { },
ClearAllegianceOfficers: () => { },
SetAllegianceOfficer: (_, _) => { },
RemoveAllegianceOfficer: _ => { },
ListAllegianceOfficerTitles: () => { },
ClearAllegianceOfficerTitles: () => { },
SetAllegianceOfficerTitle: (_, _) => { },
QueryAllegianceName: () => { },
SetAllegianceName: _ => { },
ClearAllegianceName: () => { },
AllegianceLockAction: _ => { },
SetAllegianceApprovedVassal: _ => { },
AllegianceHouseAction: _ => { },
QueryMotd: () => { },
SetMotd: _ => { },
ClearMotd: () => { },
SetOpenHouseStatus: _ => { },
AddPermanentGuest: _ => { },
RemovePermanentGuest: _ => { },
RemoveAllPermanentGuests: () => { },
ChangeStoragePermission: (_, _) => { },
AddAllStoragePermission: () => { },
RemoveAllStoragePermission: () => { },
RequestFullGuestList: () => { },
BootSpecificHouseGuest: _ => { },
BootEveryone: () => { },
SetHooksVisibility: _ => { },
ModifyAllegianceGuestPermission: _ => { },
ModifyAllegianceStoragePermission: _ => { });
}

View file

@ -1,3 +1,4 @@
using System.Collections.Frozen;
using AcDream.App.Net;
using AcDream.Core.CharGen;
using DatReaderWriter.DBObjs;
@ -258,22 +259,26 @@ public sealed class RetailSkillFormulaTests
AttributeId attributeId)
{
const uint skillId = 0x10u;
var skillTable = new SkillTable();
skillTable.Skills.Add((SkillId)skillId, new SkillBase
var options = ChargenOptions.Empty with
{
Formula = new SkillFormula
GlobalSkillDetailsBySkillId = new Dictionary<uint, ChargenSkillDetail>
{
AdditiveBonus = 0,
Attribute1Multiplier = 1,
Attribute2Multiplier = 0,
Divisor = 1,
Attribute1 = attributeId,
// Attribute2 deliberately left at its zero default (Strength) —
// Attribute2Multiplier=0 means whatever it reads contributes
// nothing, so it cannot mask a wrong Attribute1 case.
},
});
var resolver = new ChargenSkillScoreResolver(skillTable);
[skillId] = new ChargenSkillDetail(
skillId,
MinLevel: 1u,
Description: string.Empty,
new ChargenSkillFormula(
AdditiveBonus: 0,
Attribute1Multiplier: 1,
Attribute2Multiplier: 0,
Divisor: 1,
Attribute1: (uint)attributeId,
// Attribute2Multiplier=0 means the second attribute
// cannot mask a wrong Attribute1 mapping.
Attribute2: 0u)),
}.ToFrozenDictionary(),
};
var resolver = new ChargenSkillScoreResolver(options);
ChargenAttributeValues attributes = AttributeValuesWith(attributeId, 42);
uint result = resolver.Resolve(skillId, attributes, ChargenSkillAdvancementClass.Untrained);
@ -281,6 +286,19 @@ public sealed class RetailSkillFormulaTests
Assert.Equal(42u, result);
}
[Fact]
public void ChargenSkillScoreResolver_MissingProjectedSkill_ReturnsZero()
{
var resolver = new ChargenSkillScoreResolver(ChargenOptions.Empty);
uint result = resolver.Resolve(
0x10u,
new ChargenAttributeValues(10, 20, 30, 40, 50, 60),
ChargenSkillAdvancementClass.Specialized);
Assert.Equal(0u, result);
}
private static ChargenAttributeValues AttributeValuesWith(AttributeId attributeId, int value) =>
attributeId switch
{