refactor(net): own live session composition
Extract reset, selection, entered-world, and route construction behind LiveSessionHost while preserving the sole LiveSessionController authority. Retain partial route and subscription cleanup for retry, and replace the embedded ACE-only shortcut with the exact named-retail unsigned skill formula. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
18d4b999de
commit
557eb7ef6b
22 changed files with 1430 additions and 236 deletions
65
docs/research/skill_formula_calculate_pseudocode.md
Normal file
65
docs/research/skill_formula_calculate_pseudocode.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# Retail skill-formula calculation
|
||||
|
||||
## Oracle
|
||||
|
||||
- Named retail: `SkillFormula::Calculate @ 0x00591960` in
|
||||
`named-retail/acclient_2013_pseudo_c.txt`.
|
||||
- Retail layout: `SkillFormula` in `named-retail/acclient.h:40199` stores six
|
||||
unsigned 32-bit fields in wire order: `W`, `X`, `Y`, `Z`, `Attr1`, `Attr2`.
|
||||
|
||||
The signed-looking branches in the recovered x87 code are not signed formula
|
||||
semantics. They are the compiler's unsigned-32-bit-to-floating-point fix-up:
|
||||
when the high bit is set, it adds `2^32` after the initial signed conversion.
|
||||
|
||||
## Pseudocode
|
||||
|
||||
```text
|
||||
bool SkillFormula.Calculate(uint attribute1, uint attribute2, out uint result)
|
||||
{
|
||||
uint divisor = Z;
|
||||
if (divisor == 0)
|
||||
return false;
|
||||
|
||||
// Every operation on this line is uint32 and wraps modulo 2^32.
|
||||
uint numerator = X * attribute1 + Y * attribute2 + W;
|
||||
|
||||
// Formula values are non-negative. Retail rounds to nearest integer,
|
||||
// with exact halves rounding upward.
|
||||
result = uint(floor(double(numerator) / double(divisor) + 0.5));
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
Consequences pinned by the port:
|
||||
|
||||
- `Z == 0` is the only failure gate. `X == 0` remains a valid formula.
|
||||
- `W` participates in the numerator before division; it is not added after
|
||||
division.
|
||||
- multiplication and addition wrap as unsigned 32-bit arithmetic before the
|
||||
floating-point division.
|
||||
- the division rounds half-up; it does not truncate.
|
||||
|
||||
## Reference cross-check
|
||||
|
||||
ACE's [`SkillFormula`](https://github.com/ACEmulator/ACE/blob/master/Source/ACE.DatLoader/Entity/SkillFormula.cs)
|
||||
confirms the unsigned DAT order `W, X, Y, Z, Attr1, Attr2`. ACE's
|
||||
[`AttributeFormula.GetFormula`](https://github.com/ACEmulator/ACE/blob/master/Source/ACE.Server/Entity/AttributeFormula.cs)
|
||||
is only an interpretation aid: it rejects `X == 0`, assumes unit multipliers,
|
||||
omits `W`, and rounds a summed-attribute quotient. That is sufficient for
|
||||
ordinary stock ACE data but is not the general retail algorithm.
|
||||
|
||||
Chorizite DatReaderWriter's generated
|
||||
[`SkillFormula`](https://github.com/Chorizite/DatReaderWriter/blob/master/DatReaderWriter/Generated/Types/SkillFormula.generated.cs)
|
||||
independently confirms the same six-field order and maps its public names to
|
||||
`W`, `X`, `Y`, and `Z`. It exposes the four arithmetic words as signed `int`,
|
||||
so the retail port must reinterpret their bits as `uint`. Its summary places
|
||||
the additive value after division; the named retail function proves that the
|
||||
actual client adds `W` to the numerator instead.
|
||||
|
||||
## Port decision
|
||||
|
||||
`RetailSkillFormula` is the sole App-layer calculation seam. It reinterprets
|
||||
the DAT words as unsigned, performs the exact unchecked 32-bit numerator, and
|
||||
uses the retail half-up division. `LiveSkillCreditResolver` performs only the
|
||||
skill-table lookup and treats a missing attribute as zero, matching the prior
|
||||
live-property projection behavior.
|
||||
Loading…
Add table
Add a link
Reference in a new issue