fix(runtime): Campaign CC slice CC3 review-fix round — F1-F16
Opus dual-lens review of CC3's RuntimeCharacterCreationState passed on retail fidelity but failed the controller integration: the post-create log-straight-in indexed the CACHED wire CharacterList, which ACE never resends after a create (it only appends server-side and replies Ok) — with zero pre-existing characters this throws, with N it can silently enter the WRONG character. The same stale-index problem corrupted every pre-existing character's delete slot on roster re-sort. Fixes all four blocking findings plus a credit-gate correctness bug (retail warns and lets the user confirm through unspent credits; it does not force a full spend) and eight lower-severity findings from the same review round. F1 (blocking): WorldSession gained a guid-based EnterWorld(uint,string, TimeSpan?) overload sharing EnterWorldCore with the index-based one; ILiveSessionOperations gained a default EnterWorldByGuid method. LiveSessionController factored EnterSelectedCore/the new EnterCreatedCharacterCore through a shared EnterHighlightedCore so the post-create enter sends by the exact guid the 0xF643 Ok reply carried, never by a roster index. F2 (blocking): RuntimeCharacterSelectionState gained a real AppendCreatedCharacter primitive that preserves every existing entry's ActiveIndex (a wire contract — SendDeleteCharacter sends it as the CharacterSet slot) and assigns the new entry's from the pre-create wire roster count, instead of round-tripping the post-create roster through ApplyRoster's name-sort-and-renumber. F3 (blocking): retail's DoFinish(this, arg2) gate is "arg2 != 0 && remainingAtrbCredits > 0" — the ordinary click warns and refuses, but the warning dialog's own confirm re-invokes DoFinish(this, 0), which sends anyway with credits unspent (ACE accepts this). TryBeginFinish/Finish gained a confirmedUnspentCredits parameter; the plan doc's "retail FORCES full spend" line is corrected in the same commit. F4 (blocking): a stale out-of-range template index surviving a heritage switch to a heritage with fewer templates now clears to TemplateUnset, matching ConstrainAllByHeritage's clamp. F5/F9/F10: three register-row/doc citation corrections (AP-207's real FitTemplateToCharacter call sites — a fourth one the original filing also missed; the Slot field's real retail assignment source; AP-209's classID branch table for Olthoi/OlthoiAcid). F6: ApplyCreationResponse no longer publishes from inside the owner lock. F7: two new tests pin BalanceAttributes' persistent donor cursor (successive-overspend advance, Self-to-Strength wrap). F8: ResetSkillLevels' doc corrected to retail's real both-costs->=0 gate. F11: the integration test fixture captures guid-based enter calls and uses two pre-existing characters whose wire order differs from alphabetical order, so the roster assertion actually exercises F2 instead of coinciding with it by accident. F12: filed register row AP-211 for the client-side RosterFull slot-cap refusal (no retail DoFinish-layer counterpart). F13: narrowed Finish's bare catch to InvalidOperationException/SocketException and bound _scope to a local. F15: RandomizeStartAreaLocked leaves the start area unchanged on an empty list instead of forcing -1, matching retail. Runtime 1706/0 (was 1701), Core.Net unchanged at 994/0, full solution Release build green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9a84230c4f
commit
397ccd62cd
9 changed files with 578 additions and 68 deletions
|
|
@ -334,6 +334,53 @@ public sealed class RuntimeCharacterSelectionState : IDisposable
|
|||
selected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign CC slice CC3 review-fix round (F2): appends ONE freshly
|
||||
/// created character without re-deriving every entry's
|
||||
/// <see cref="RuntimeCharacterSelectionEntry.ActiveIndex"/> from display
|
||||
/// order the way <see cref="ApplyRoster"/> does. <c>ActiveIndex</c> is a
|
||||
/// WIRE CONTRACT — <c>SendDeleteCharacter</c> sends it as the
|
||||
/// CharacterSet slot and ACE indexes
|
||||
/// <c>session.Characters[(int)characterSlot]</c>
|
||||
/// (<c>references/ACE/Source/ACE.Server/Network/Handlers/CharacterHandler.cs:297</c>)
|
||||
/// — so round-tripping the post-create roster through
|
||||
/// <see cref="ApplyRoster"/>'s name-sort would silently retarget every
|
||||
/// PRE-EXISTING character's delete slot to its alphabetical rank.
|
||||
/// <paramref name="wireIndex"/> is the caller-supplied wire slot for the
|
||||
/// NEW entry only (ACE appends to <c>session.Characters</c>, so the new
|
||||
/// character's slot equals the wire roster's count BEFORE this create,
|
||||
/// 0-based — the caller must read that from the cached wire source, not
|
||||
/// from the sorted display mirror this class exposes). Every existing
|
||||
/// entry's <see cref="RuntimeCharacterSelectionEntry.ActiveIndex"/> is
|
||||
/// copied through untouched; only the array's DISPLAY order (name sort,
|
||||
/// greyed-to-tail) is recomputed, exactly like <see cref="ApplyRoster"/>'s
|
||||
/// own sort. Does not touch highlight — callers that want the new entry
|
||||
/// selected still call <see cref="TryHighlight"/> afterward.
|
||||
/// </summary>
|
||||
internal void AppendCreatedCharacter(uint characterId, string name, int wireIndex)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
lock (_gate)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var appended = new RuntimeCharacterSelectionEntry[_entries.Length + 1];
|
||||
Array.Copy(_entries, appended, _entries.Length);
|
||||
appended[^1] = new RuntimeCharacterSelectionEntry(
|
||||
wireIndex,
|
||||
characterId,
|
||||
name,
|
||||
SecondsGreyedOut: 0u);
|
||||
|
||||
Array.Sort(
|
||||
appended,
|
||||
static (left, right) =>
|
||||
string.CompareOrdinal(left.Name, right.Name));
|
||||
_entries = StablePartitionGreyedToTail(appended);
|
||||
_revision++;
|
||||
}
|
||||
Publish(RuntimeCharacterSelectionDeltaKind.RosterChanged, characterId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign LA gate round 2 finding 3: retail's <c>UpdateWorldName</c>
|
||||
/// (<c>0x004ec120</c>) / <c>RecvNotice_WorldName</c> (<c>0x004ec360</c>)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue