Conversation
…llback Conversion now covers the palette-dependent tier: - stc*.hpf statics via the archive's stc TBL lookup (RenderMap's +1 keying) - MPF monsters via the embedded palette number into mns*.pal (sibling hades.dat for strays) - efct*/mefc* effect EPFs via effpal.tbl / mefcpal.tbl - item*.epf icon sheets via itempal.tbl at a fixed 266-icon stride (sheets may carry fewer frames than capacity; every range boundary in the retail table is congruent to 1 mod 266) - khan part EPFs: <gender m|w><part letter><sprite:D3><anim> naming; the part letter selects the khanpal.dat palette family, letters sharing families (a/b/n -> palb, c/g/j -> palc, p/s -> palp) and the body/face letters (m/o) using the palm0-9 skin-tone ramp; male and female palette overrides honored - ground tiles rendered from the tilea bank via the mpt lookup (+2 keying), into a tiles/ subdirectory - single-palette-era archives (3.61's merged DarkAges.dat carries no family palette tables): legend.pal renders everything, gated on the archive having no *pal.tbl so modern archives never fall back Animations: --animate emits looping APNG (in-repo chunk splicer over Skia per-frame encodes; no new dependency), --gif emits animated GIF via KGySoft's encoder (already in the dependency closure). EFA files play at their embedded FrameIntervalMs; MPF named sequences (standing, walk, attack, attack2, attack3) play at AnimationIntervalMs, treating the >= 10000 values carried by 294 of 581 retail monsters as a no-authored-interval sentinel (200ms fallback); EPF effects play in file order at 100ms since Effect.tbl sequences carry frame selectors but no timing (the interval is server-supplied at runtime). Cross-archive palettes (khanpal.dat, hades.dat) resolve lazily from siblings of the input via a per-run ConversionContext that also caches palette lookups across entries. Verified against the 7.41 corpus: 55,518 entries -> 536,125 images, zero failures, 99%+ of image-format entries converted (the remainder is empty placeholder EPFs and UI families with client-hardcoded palettes). 3.61: 52,693 images via the legend.pal fallback. 17 tests pin the conventions at pixel level, including two review-caught fixes: the item stride must be the 266 constant (item041 has 247 frames), and the khan sprite id is the 3-digit field, not the whole digit run (mu05501 is sprite 55 anim 01 - the old parse silently defaulted most khan palettes). Co-Authored-By: Imbas <imbas@eris.co>
Angelique's TypeScript port (dalib-ts, agent Fintan) surfaced these while
reconciling against a real Dark Ages 7.41 client and the darkages-741-re
docs. Each was re-verified here against the RE docs and, where possible,
against real retail assets before fixing.
1. ControlFileParser: <IMAGE> is an explicit ordered list of (name, frame)
visual states, not a start/end range. Range-filling fabricated frames for
sparse lists. Confirmed live on retail data: field001.txt has
emot000.epf {0,1,3} and emot001.epf {0,1,7}, which the old code expanded
to {0,1,2,3} and {0..7}, shifting every later emote button state.
2. Graphics.RenderTile: ground tiles are isometric diamonds. Inside the
diamond palette index 0 is an ordinary opaque color (not transparent as
for sprites), and pixels outside the diamond are masked transparent. The
old path routed tiles through the sprite SimpleRender, dropping index-0
ground pixels (holes) and applying no mask.
3. MapFile: cells are u16le. A foreground id above 32767 (stc ids are five
digits) read as a negative short and was then treated as non-renderable.
Also, the client accepts a longer-than-expected file and ignores the
trailing bytes; the old strict-equality length check rejected it.
MapTile id fields widened short -> ushort (breaking; wire-faithful).
4. HeaFile: a run's intensity byte carries flags in its top two bits; mask
with & 0x3F at decode. MAX_LIGHT_VALUE is 0x20, so a flag bit corrupted
the intensity.
5. SpfFile/SpfView: frame size is the visible rectangle (Right-Left x
Bottom-Top), and source rows advance by ByteWidth (pitch). The old decode
used absolute Right/Bottom and ignored pitch, so nonzero-origin or padded
frames decoded wrong. No shipped 7.41 asset has such a frame, so this is
robustness verified by synthetic tests.
6. PaletteTable: strip // comments and split on whitespace runs in both the
range parser and ParseCyclingFile. The client skips blank lines and //
comments; the old single-space split dropped commented/padded entries.
Adds 11 regression tests (945 total, all passing).
Co-Authored-By: Imbas <imbas@eris.co>
The 2026-07-23 code review of 523b149 confirmed a pre-existing bug this commit now closes: colorized SPFs with more than one frame decoded garbage. ReadColorized read frames sequentially and consumed only the first (RGB565) of each frame's two on-disk pixel copies, so frame 1+ started inside frame 0's RGB555 copy. (The StartAddress seek that would have realigned it was on a StreamSegment the loop never read from — dead code.) - ReadColorized now seeks to dataSectionStart + StartAddress per frame, reads only the visible RGB565 rectangle (honoring pitch), and allocates ColorData compact (also removes a ~2x over-allocation). - SaveColorized derives sizes/strides from the visible rectangle (PixelWidth/PixelHeight) instead of absolute Right/Bottom, so decode and save agree — closing the asymmetry 523b149's relative-dims change left for a nonzero-origin colorized frame. For real assets (origin 0, pitch==width) every bound and size is unchanged; verified by the full suite (947) plus clean setoa/national converts. Adds two multi-frame colorized regression tests (decode-from-StartAddress and a FromImages save/load round-trip). SpfFile and SpfView now agree on multi-frame colorized output. Co-Authored-By: Imbas <imbas@eris.co>
Foscail is versioned and released independently of DALib but lives in the same repo. This workflow mirrors dalib-package.yml, keyed to a separate foscail-v* tag namespace so a tool release never collides with a DALib library release. Packs only Foscail/Foscail.csproj (DALib.dll is bundled), signs, and pushes Foscail.<version>.nupkg to nuget.org. Co-Authored-By: Imbas <imbas@eris.co>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Continues the
foscailbranch past the milestone-1 merge (#18). Five commits: foscail milestone-2, the dalib-ts reconciliation fixes, a follow-up SPF fix, version bumps, and an independent Foscail publish workflow. Full suite green (947 tests).Foscail milestone-2 (
86c6642)Palette-dependent asset conversion for every family (stc/mpf/epf/khan/tiles), APNG + GIF animation export, the 3.61 single-palette
legend.palfallback. 7.41 corpus → ~531k images, 3.61 → ~52k, zero failures.Six DALib file-format fixes (
523b149)Bugs surfaced by the
dalib-tsTypeScript port reconciling against a real 7.41 client + thedarkages-741RE docs; each re-verified here against ground truth before fixing. Ranked by impact:<IMAGE>— kept as an explicit ordered frame list instead of range-filling. Retail-verified: shippedfield001.txthasemot000.epf {0,1,3}andemot001.epf {0,1,7}, which the old range-fill expanded to{0,1,2,3}/{0..7}, fabricating frames and shifting emote button states.RenderTilenow draws the isometric diamond (index-0 opaque inside, masked outside) instead of routing through the spriteSimpleRender, which dropped index-0 ground pixels as holes.u16le(a foreground id ≥ 32768 read negative and was then skipped as non-renderable); length check accepts trailing bytes.MapTileid fieldsshort → ushort(breaking, wire-faithful).& 0x3F(top two bits are flags).Right-Left×Bottom-Top) and honorsByteWidthpitch.//comments and tolerates whitespace runs, matching the client's parser.Colorized-SPF rework (
ee57157)A two-agent code review of
523b149confirmed (via a parent-commit worktree probe) a pre-existing bug: multi-frame colorized SPFs decoded garbage because frames were read sequentially while the format stores two pixel copies (565 + 555) each, and the per-frameStartAddressseek was dead code.ReadColorizednow seeks per-frame;SaveColorizedmoved onto relative dims so decode/save agree. No real 7.41 asset is multi-frame colorized, so this is covered by synthetic round-trip tests.Versions + release wiring (
38a6a7e,1a342a2)1.0.0-beta2 → beta3, Foscail1.0.0-beta1 → beta2(more testing wanted before 1.0).foscail-package.ymlpublishes the tool on its ownfoscail-v*tags (mirrorsdalib-package.yml; packs only Foscail, with DALib.dll bundled). Foscail stays in this monorepo and is versioned independently — rationale and split criteria in the imbasfoscail-packaging-decisionnote.Publishing note
Merging lands the code on
mainbut ships nothing — publishing is tag-triggered. After merge, pushv1.0.0-beta3(→ DALib) andfoscail-v1.0.0-beta2(→ Foscail) to publish each to nuget.org.Testing
Generated with Imbas imbas@eris.co