diff --git a/src/AcDream.Core/Chat/WeenieErrorMessages.cs b/src/AcDream.Core/Chat/WeenieErrorMessages.cs index bc1abb22..26a4bf00 100644 --- a/src/AcDream.Core/Chat/WeenieErrorMessages.cs +++ b/src/AcDream.Core/Chat/WeenieErrorMessages.cs @@ -254,6 +254,28 @@ public static class WeenieErrorMessages [0x413u] = new("%s is already one of your followers", RetailLogTextType.ClientLocal), [0x414u] = new("You are not in an allegiance!", RetailLogTextType.ClientLocal), [0x416u] = new("%s cannot have any more Vassals", RetailLogTextType.ClientLocal), + // Campaign FA slice FA1 (2026-08-11): 0x0417-0x041C + // (FellowshipIgnoringRequests/FellowshipSquelched/ + // FellowshipMaxDistanceExceeded/FellowshipMember/ + // FellowshipIllegalLevel/FellowshipRecruitBusy) are INTENTIONALLY + // absent — verified, not overlooked. Re-ran this table's own + // methodology (the class-doc's binary sweep) specifically for + // these six ids: `grep`-walked the ENTIRE 1.4M-line + // acclient_2013_pseudo_c.txt for every literal/case-label + // comparison against each of 0x417..0x41c and found ZERO hits + // anywhere in the file, then independently walked + // HandleFailureEvent's own ~3350-line case-label list + // (0x40a..0x489 switch block) and confirmed the sequence goes + // ...case 0x414: case 0x416: case 0x41d:... with NO case for + // 0x417-0x41c in between. Retail's Sept-2013 client has no + // display text for any of these six ids — they fall through to + // the "no case matched" outcome this table already models via + // Resolve()'s null-Text/no-display return. ACE never sends any of + // them (only 0x0417 and 0x04DB below are on a live ACE send path, + // and 0x0417 shows nothing in retail regardless). Do not add rows + // for these — that would be invented English with no retail + // anchor, exactly the class of mistake SHOULD-FIX 4 fixed for the + // table's no-default-case rule. [0x41Du] = new("You must be the leader of a Fellowship", RetailLogTextType.ClientLocal), [0x41Eu] = new("Your Fellowship is full", RetailLogTextType.ClientLocal), [0x41Fu] = new("That Fellowship name is not permitted", RetailLogTextType.ClientLocal), @@ -376,6 +398,17 @@ public static class WeenieErrorMessages [0x4D8u] = new("You have succeeded in untraining your %s skill!", RetailLogTextType.Default), [0x4D9u] = new("Although you cannot untrain your %s skill, you have succeeded in recovering all the experience you had invested in it.", RetailLogTextType.Default), [0x4DAu] = new("You have too many credits invested in specialized skills already! Before you can specialize your %s skill, you will need to unspecialize some other skill.", RetailLogTextType.Default), + // Campaign FA slice FA1 (2026-08-11): 0x04DB (FellowshipDeclined) + // and 0x04DC (FellowshipTimeout) are INTENTIONALLY absent, same + // finding as the 0x0417-0x041C block above. The switch's case + // sequence in this address range goes + // ...case 0x4da: case 0x4dd:... — 0x4db/0x4dc have no case. This + // is the sharper of the two gaps: ACE actually sends + // FellowshipDeclined (Fellowship.cs:147, on a recruit refusal), + // so a live server event genuinely produces NO text in the + // Sept-2013 client — acdream's current silent-drop behavior for + // it is therefore retail-faithful, not a bug to fix by inventing + // a line retail never showed. [0x4DDu] = new("You have failed to alter your attributes.", RetailLogTextType.Default), [0x4DEu] = new("%s", RetailLogTextType.Default), [0x4DFu] = new("%s", RetailLogTextType.Default), diff --git a/tests/AcDream.Core.Tests/Chat/WeenieErrorMessagesTests.cs b/tests/AcDream.Core.Tests/Chat/WeenieErrorMessagesTests.cs index f24b98ca..95f772b3 100644 --- a/tests/AcDream.Core.Tests/Chat/WeenieErrorMessagesTests.cs +++ b/tests/AcDream.Core.Tests/Chat/WeenieErrorMessagesTests.cs @@ -365,4 +365,30 @@ public sealed class WeenieErrorMessagesTests Assert.Equal("A drudge fails to affect you because $s cannot affect anyone!", text); Assert.Equal(RetailLogTextType.Magic, type); } + + // ── Campaign FA slice FA1 (2026-08-11): the 8 "missing" fellowship ids ── + // + // Same class of proof as Format_0x051D_ReturnsNull_NoRetailCaseExists + // above: independently re-ran this file's own binary-sweep methodology + // (a full-text grep of acclient_2013_pseudo_c.txt for every + // literal/case-label comparison against these 8 ids, plus a manual + // walk of HandleFailureEvent's own case-label sequence around each) + // and found NO case for any of them anywhere in the Sept-2013 client. + // These are intentionally absent from the table, not overlooked — + // adding invented text for them would itself be the SHOULD-FIX-4 class + // of bug this table exists to avoid. + [Theory] + [InlineData(0x0417u)] // FellowshipIgnoringRequests — ACE sends this live; retail still shows nothing + [InlineData(0x0418u)] // FellowshipSquelched — ACE never sends it + [InlineData(0x0419u)] // FellowshipMaxDistanceExceeded — ACE never sends it + [InlineData(0x041Au)] // FellowshipMember — ACE never sends it + [InlineData(0x041Bu)] // FellowshipIllegalLevel — ACE never sends it + [InlineData(0x041Cu)] // FellowshipRecruitBusy — ACE never sends it (busy is a plain chat line instead) + [InlineData(0x04DBu)] // FellowshipDeclined — ACE sends this live; retail still shows nothing + [InlineData(0x04DCu)] // FellowshipTimeout — ACE never sends it + public void Resolve_FellowshipIdsAbsentFromHandleFailureEvent_ReturnsNoText(uint code) + { + var (text, _) = WeenieErrorMessages.Resolve(code, param: null); + Assert.Null(text); + } }