namespace AcDream.Core.CharGen; /// /// Pure port of retail's shade→palette-index resolution /// (PalSet::GetPaletteID @ 0x005AC570, invoked from /// gmCG3DView::Update @ 0x004EE9D0 for the skin/hair subpalette /// build and from ClothingTable::BuildObjDesc @ 0x005A7900 for every /// clothing-slot dye choice). The decompiled body is genuinely FPU-elided — /// the _ftol2() truncating-cast operand is lost to the decompiler, /// and can only be read as "some product of -ish and /// -ish operands" from the surrounding x87 stack /// traffic — but the decomp's own control-flow SHAPE is still verifiable /// independent of that lost operand: a two-sided FPU compare at /// 0x005AC5A0 gating on >= 0.0, consistent with a /// [0,1] shade bounds check before the cast. What resolves the /// elided operand is ACE's ACE.DatLoader.FileTypes.PaletteSet.GetPaletteID, /// which carries the explicit comment "Taken from acclient.c /// (PalSet::GetPaletteID)" against the exact formula below. That is TWO /// sources (decomp control flow + ACE's cited port), not three: the /// PaletteSet.cs file present in the vendored ACViewer checkout is /// ACE's own file, not an independent reimplementation, and ACViewer's /// ClothingTableList.xaml.cs:97 UI slider computes a DIFFERENT /// expression for a DIFFERENT problem (mapping a shade back to a slider tick /// position against Shades.Maximum, i.e. count-1, not /// count) — neither corroborates this formula and both are dropped /// from the evidence chain here. /// public static class ChargenPalSetMath { /// /// Resolves a shade fraction to an index into a palette-id list of the /// given . Returns -1 (retail's /// INVALID_DID outcome) when is /// non-positive or falls outside /// [0.0, 1.0] — including retail's own -1.0 "unset" /// sentinel (CharGenState::Reset @ 0x005C68A0), which is /// deliberately out of range so an untouched shade resolves to /// "nothing," matching retail. Callers should treat -1 as "skip this /// subpalette contribution" rather than emit a placeholder id. /// public static int GetPaletteIndex(int count, double shade) { if (count <= 0 || shade < 0.0 || shade > 1.0) return -1; // Truncating cast, exactly as ACE's cited port and the decomp's // _ftol2() (which truncates toward zero on x86, matching a plain // C-style (int) cast here since count > 0 and 0 <= shade <= 1 keep // the product non-negative). int index = (int)((count - 0.000001) * shade); if (index < 0) index = 0; if (index > count - 1) index = count - 1; return index; } }