Updated knowledge

This commit is contained in:
MaddoScientisto 2026-04-02 01:15:16 +02:00
commit 1ad746ba82
21 changed files with 882 additions and 9 deletions

View file

@ -0,0 +1,187 @@
# Editor-Item Animation In Retail Crusader And The Viewer
This note closes the current map-viewer animation lane for editor/helper objects that flash, cycle colors, or step through multiple frames.
The key result is that two different animation systems overlap in this lane:
- frame animation from Crusader `TYPEFLAG.DAT`
- palette-slot animation from the shared `CycleProcess` rows written into palette entries `8..14`
The viewer now reproduces both, with one important caveat:
- the frame-selection semantics are evidence-backed, but the exact retail wall-clock tick length is still not directly closed from the live DOS executable, so the viewer uses the recovered `animType/animData/animSpeed` rules with a viewer-side step cadence that stays in family with the already-recovered palette cycle
## Short Answer
The flashing editor walls and helper surfaces are not a separate editor-only special effect process.
Current best model:
1. many editor/helper shapes are tagged `SI_TRANSL` and use the low Crusader translucent xform slots
2. those low slots map heavily to source palette indices `8..14`
3. `CycleProcess_Update` advances those same palette rows globally
4. when editor/helper sprites use those slots, they inherit the flashing/cycling colors automatically
5. some shapes also have nonzero `animType`, so they change frame as well as color
## Evidence Base
### Live `CRUSADER.EXE` / Ghidra side
- `CycleProcess_Update` at `1438:011b` advances the shared palette-cycle rows and writes them back through `SuperVGA_SetPaletteColor` at `1438:0366`
- the same recovered cycle rows already explained the F7-family overlay colors in [docs/f7-overlays.md](docs/f7-overlays.md)
- `Gamepal_InitXformpalDatStruct` / `Gamepal_LoadXformpalDat` / `Gamepal_ReadXformpal_1028_0348` at `1028:01db`, `1028:0250`, and `1028:0348` are the live xform-palette load path
- `ItemType_LoadTypeflagDat` / `Item_GetTypeflagData` at `10f8:0275` / `10f8:0336` are the live typeflag load/access anchors for the 9-byte Crusader `TYPEFLAG.DAT` rows
- `Item_PaintSprite` at `1198:02e4` still skips `SI_EDITOR` in the normal gameplay renderer, so most of this animation is normally only visible on debug/editor/helper lanes unless another overlay or engine-side viewer exposes those objects
### Renderer-side direct pixel evidence
The earlier translucency pass already established that representative editor/helper translucent shapes are dominated by source palette slots `8`, `9`, `10`, `11`, `13`, and sometimes `14`.
Representative probe families:
- invisible/editor walls `0x005A..0x0069`
- helper `0x00E9`
- `Zappy_Surface_*` `0x044D` / `0x044E`
That evidence is recorded in [docs/map_renderer/translucency-xformpal.md](translucency-xformpal.md).
### Open-source Crusader engine cross-check
The missing frame-animation semantics are closed well enough by the Crusader-specific loaders in ScummVM and Pentagram:
- Crusader `TYPEFLAG.DAT` is a 9-byte format
- byte `4` high nibble = `animType`
- byte `5` low nibble = `animData`
- byte `5` high nibble = `animSpeed`
- byte `6` bit `0` = `SI_EDITOR`
The ScummVM Crusader item update path also preserves a concrete `animateItem()` switch for `animType` values `1..6`, which is enough to reproduce the frame-selection rules even though the exact DOS tick duration is still not directly timed in the live retail pass.
## Retail Model
### 1. Shared palette-cycle rows drive the flashing colors
This part is already directly evidenced from the live NE executable.
- `CycleProcess_InitColorTables` seeds the cycle rows
- `CycleProcess_Update` advances them
- the rows are written back into live palette entries `8..14`
Those same rows already drive:
- plain `F7`
- `Alt+F7`
- `Ctrl+F7`
The editor/helper flashing lane reuses that same machinery rather than inventing a separate color animator.
### 2. XFORMPAL makes the low slots matter for translucent editor/helper shapes
The editor/helper walls and related helper surfaces are often translucent, and their source pixels disproportionately use the low slot family `8..14`.
That means their apparent color is effectively delegated to the current shared cycle rows.
In practice, the visible rule is:
- if a translucent editor/helper sprite is built from those low slots, its hues will flash when the shared cycle rows change
### 3. Some editor/helper shapes also have real frame animation
Crusader typeflags also carry per-shape animation metadata.
Closed field layout for the Crusader 9-byte row:
- `animType` = byte `4` high nibble
- `animData` = byte `5` low nibble
- `animSpeed` = byte `5` high nibble
Recovered/open-source animation behaviors used by the viewer:
- `animType 1` / `3`: increment through frames; `animData` chooses unconditional, 50%, or block-loop behavior
- `animType 2`: random frame changes
- `animType 4`: random start, then run through frames
- `animType 5`: usecode-driven animation hook; viewer leaves this as static because the live script side is not reproduced here
- `animType 6`: loop from frame `1` while leaving frame `0` as a resting/sentinel state
## Viewer Implementation
### Metadata fixes
The map renderer now carries all three Crusader animation fields through the exported shape definition:
- `animType`
- `animData`
- `animSpeed`
The tooltip trait list now shows all three when present.
### Atlas/reference change
Static scene exports previously only guaranteed the specific frame used by the authored map item.
That is not enough for animated shapes.
The reference build now expands visible shapes with nonzero `animType` to include every frame from the shape archive, so the client can step frames at render time instead of getting stuck on the authored start frame.
### Palette-cycle reproduction
The client now carries a compact rendering descriptor for the low xform-cycle slots:
- source slot `8..14`
- the baked atlas RGBA that corresponds to the current XFORMPAL-remapped translucent output
At render time, translucent editor/helper sprites are copied into a small sprite canvas and any pixel matching one of those slot RGBA values is recolored from the current shared cycle-row RGB for the same slot.
That lets the viewer animate the same low-slot translucent editor/helper art without inflating the scene payload with raw pixel streams.
### Frame animation reproduction
The client now steps `animType` shapes using the recovered Crusader rules above.
Current approximation boundary:
- the semantic frame-advance rules are evidence-backed
- the exact retail wall-clock tick size is still not directly timed from live DOS execution
- the viewer therefore uses a stable viewer-side animation step that stays aligned with the already recovered palette-cycle cadence instead of pretending to know the exact original millisecond rate
That is good enough to reproduce the visible behavior for map-viewer/editor purposes without overclaiming a closed retail timing constant.
## Practical Scope In The Viewer
The current viewer pass targets the objects the user actually asked about:
- editor-tagged shapes
- helper/occluding editor geometry
- translucent editor/helper surfaces that inherit the low cycle slots
The frame-animation support is intentionally a little broader than the pure color-cycle support, because `animType` is real shape metadata and some animated map shapes are not strictly editor-only.
The palette-cycle recolor stays narrower and only applies to the translucent editor/helper lane where the evidence is strong.
## What Is Closed Versus Still Open
### Closed enough for the viewer
- the shared flashing colors come from `CycleProcess` rows written into palette entries `8..14`
- translucent editor/helper sprites really do depend heavily on those same low slots
- Crusader `TYPEFLAG.DAT` carries real `animType/animData/animSpeed` fields in the 9-byte row
- the viewer can now reproduce both the frame-step and palette-cycle sides in a defensible way
### Still open
- the exact live DOS wall-clock interval behind the per-item `gametick` animation updates
- whether any remaining helper families need a different XFORMPAL table than the current viewer default for hue reproduction
- the full live usecode behavior behind `animType 5` shapes
## Ghidra Notes Applied In This Batch
This batch also adds concise live-DB comments to keep the provenance visible at the key shared anchors:
- `10f8:0336` for the Crusader 9-byte typeflag animation/editor field layout
- `1438:011b` for the shared palette-cycle row reuse by both F7 overlays and translucent editor/helper colors
## Related Notes
- [docs/map_renderer/translucency-xformpal.md](translucency-xformpal.md)
- [docs/editor-object-visibility.md](../editor-object-visibility.md)
- [docs/f7-overlays.md](../f7-overlays.md)

View file

@ -24,6 +24,14 @@ This pass widened the renderer research beyond egg and NPC spawner objects and f
- placeholder cubes and placeholder UI markers
- auto-derived helper shapes tied to specific USECODE families like `WALLGUN`
## Focused Caution: Suspicious Map Objects Are Not Always Helpers
- The map-13 jump-start follow-up around the rare jump-through wall found one especially suspicious nearby placement: `fixed:4767`, shape `0x0135`, frame `0`, at world `47966,53598,97` in the decoded retail cache.
- That object looks tempting as an editor/helper candidate when viewed only from map placement, but the decoded reference data says otherwise: `0x0135` is `shape:309`, a `terrain` item with dimensions `4 x 4 x 0` and traits `solid`, `fixed`, and `land`.
- The useful classification came from USECODE rather than from the exported editor/helper buckets. In the extracted corpus, class `0x0135` is `FFFLOOR`, an environmental hazard/controller family with live `gotHit`, `equip`, and `unequip` bodies.
- The nearby map-13 companion object is not an editor wall flag or a hidden collision override either. The closest local trigger on the same upper platform is the family-4 egg `fixed:4770` (`shape 17`, egg id `37`, subtype selector `QLo 4`), which currently resolves to `CHANGER`, not to a direct wall-solidity helper.
- Practical renderer implication: when a placement looks suspicious in map context, do not assume it belongs in the editor/helper bucket just because it sits beside editor markers. `0x0135` is a good counterexample: it is a gameplay-side environmental floor tile that only becomes legible once the USECODE class is identified.
## Implemented UI Enrichment
The tooltip now exposes generalized metadata for editor/helper objects instead of reserving extra detail almost entirely for NPC spawners:

View file

@ -78,7 +78,7 @@ Current Crusader viewer work now closes one additional family-4 detail for the `
- `quality & 0xFF` is the subtype selector for the authored family-4 usecode class.
- The runtime resolves that class as `0x0900 + QLo`.
- The currently verified authored subtype sets are:
- Remorse: `QLo 0, 1, 2, 13` -> `TRIGEGG`, `ONCEEGG`, `FLOOR1`, `MISS1EGG`
- Remorse: `QLo 0, 1, 2, 4, 13` -> `TRIGEGG`, `ONCEEGG`, `FLOOR1`, `CHANGER`, `MISS1EGG`
- Regret: `QLo 0, 1, 2, 5, 8, 10, 13, 24` -> `TRIGEGG`, `ONCEEGG`, `FLOOR1`, `MHATCHER`, `CHANGER`, `DOOREGG`, `MISS1`, `VIDEOEGG`
- `npcNum` does not behave like a DTABLE row here.
- `xRange = (npcNum >> 4) & 0x0f`
@ -88,6 +88,15 @@ Current Crusader viewer work now closes one additional family-4 detail for the `
That is why the renderer now treats `0x0011` as a proximity/usecode-trigger egg with a projected footprint overlay, a subtype-aware USECODE landing point, and only the narrower local-arrow rules that are actually justified by the recovered subtype body.
One checked Remorse example now makes the `CHANGER` subtype concrete.
- Map 13 `fixed:4770` is `item:12473:fixed:17:0:47888:53256:96` with `mapNum = 37`, `quality = 4`, and `npcNum = 64`.
- `quality & 0xff = 4` resolves the usecode class to `0x0904`, which matches the extracted Remorse `CHANGER::hatch` body.
- `CHANGER::hatch` reads `eggNum = Egg.getEggId(arg_06)` from `mapNum`, walks nearby `roof` candidates, compares each candidate's low quality byte against that egg id, and destroys the matching roof when `Item.getQLo(roof) == eggNum`.
- The same local decoded scene contains nearby roof placements (`shape:538`, kind `roof`) whose `quality & 0xff = 37`, matching the egg id from `mapNum`.
Current safest read for `CHANGER` is therefore `keyed roof-destruction trigger`, not generic collision override logic and not a Regret-only family-4 subtype.
### Monster eggs
ScummVM's monster egg accessor exposes:

View file

@ -68,10 +68,11 @@ Current conclusion: `0x024F` frame `0` monster eggs are not the same authoring f
### `0x04D0` Field Ambiguity Notes
- The strongest current activation control lives in `MONSTER.slot_0F enterFastArea`, not in DTABLE. That script only checks `0x04D0` objects when `frame == 0`, then blocks the automatic enter-area lane if `mapNum & 0x08` is set.
- The strongest current activation control lives in `MONSTER.slot_0F enterFastArea`, not in DTABLE. That script only checks `0x04D0` objects when `frame == 0`, then reaches the automatic enter-area lane when `(mapNum & 0x08) == 0`.
- Current safest read: `frame 0` is the only state that participates in the automatic `enterFastArea` spawn path, while `frame 1` skips that hook and is therefore more likely to be used in paired or externally signaled setups.
- Confirmed map-1 and map-248 pairs now also show that `frame 0 controller` should not be collapsed into `visible spawned NPC`. In the strongest checked auto-enabled cases, the paired frame-1 record is the better practical preview of the NPC that actually appears.
- `mapNum` is not consistently populated across `0x04D0` objects. Some records carry a non-zero map value and others leave it at `0`, so the field is probably contextual control data rather than a universal NPC selector.
- Within that contextual control data, bit `0x08` is now evidence-backed as an `auto-enter disabled` flag for the `MONSTER.enterFastArea` lane.
- Within that contextual control data, bit `0x08` is now evidence-backed as an `auto-enter blocked` flag for the `MONSTER.enterFastArea` lane.
- `quality` is also not specific to the DTABLE row. For example, `quality = 1285` shows up on unrelated non-`0x04D0` shapes in the exported scenes, so that value should not be read as proof of a particular NPC identity.
- `quality` low byte still does not look like the primary `spawn immediately vs wait` control. The current exported scripts do not use it in `MONSTER.enterFastArea`, although Regret `ALARMHAT` does compare nearby `0x04D0` `Item.getQLo(...)` values against difficulty lanes `0/1/2` before equipping those helpers.
- DTABLE row `0` is named `Crusader`, but the open-source engine does not use DTABLE row `0` to bootstrap the player. ScummVM's `CruGame::startGame()` takes the main actor stats from `getNPCDataForShape(1)`, while the generic Crusader actor-creation path accepts `npcNum = 0` as an ordinary DTABLE index.
@ -164,6 +165,13 @@ For editable fixed-record `0x04D0` items, the inspector now also exposes two evi
The side panel now also exposes a dedicated `Monster Spawners` audit list for `0x04D0` records, including a filter for the `auto-enter blocked` subset. Clicking an entry centers and pins that spawner so its raw fields and editable controls can be audited quickly.
Recent viewer follow-up tightened that audit lane further:
- fixed-record `0x04D0` items now surface their stable `fixed:<mapSourceIndex>` ids prominently and provide copy buttons
- the tooltip keeps the explicit `☑ auto-enabled` versus `☒ dormant` state label, while the scene preview itself now carries the color signal instead of a separate on-map checkbox badge
- tooltip and list wording now separate the verified frame-0 control lane from the current practical frame-1 preview heuristic instead of asserting that frame 0 always supplies the final visible NPC
- paired `0x04D0` previews now use a single carrier per pair instead of drawing both records: blue for the currently active preview carrier, red for dormant controller previews
The viewport's `Show verified link arrows` overlay now draws two evidence-backed link families:
- teleport eggs point from teleporter eggs to teleport destinations that share the same teleport ID
@ -180,7 +188,9 @@ When a valid DTABLE row is present, the viewport renders a semitransparent blue
- `0x04D0` DTABLE-backed editor spawners
- `0x024F` frame `0` Remorse monster eggs that carry a non-zero `npcNum`
The current renderer uses frame `0` for NPC previews by default, except for `Observer`, which is forced to frame `0x00F` because the earlier frames are blank/broken in the retail assets.
The current renderer still uses each resolved row's known preview frame, except for `Observer`, which is forced to frame `0x00F` because the earlier frames are blank/broken in the retail assets. For paired `0x04D0` rows, the UI now treats frame-1 previews as the stronger practical cue in confirmed auto-enabled examples, even though the frame-0 control path remains the verified automatic trigger lane.
The stronger reason for that rule is no longer just a few hand-picked examples. A broader cache scan across the active Remorse map-1 and map-248 exports found many auto-enabled mismatched valid pairs plus many auto-enabled cases where the frame-0 controller row does not resolve to a valid Remorse DTABLE preview at all while the paired frame-1 row does. That is why the renderer now suppresses duplicate pair ghosts instead of tinting both sides.
Representative exported scene pairs:

View file

@ -65,14 +65,15 @@ That is why the viewer opens `TRIGGER.slot_20` for pinned `0x04B1` helpers inste
- `quality & 0xFF` is the subtype selector for this family.
- The runtime resolves the usecode class as `0x0900 + QLo`.
- Current authored subtype sets are:
- Remorse: `0, 1, 2, 13` -> `TRIGEGG`, `ONCEEGG`, `FLOOR1`, `MISS1EGG`
- Remorse: `0, 1, 2, 4, 13` -> `TRIGEGG`, `ONCEEGG`, `FLOOR1`, `CHANGER`, `MISS1EGG`
- Regret: `0, 1, 2, 5, 8, 10, 13, 24` -> `TRIGEGG`, `ONCEEGG`, `FLOOR1`, `MHATCHER`, `CHANGER`, `DOOREGG`, `MISS1`, `VIDEOEGG`
- `npcNum` packs `xRange = high nibble` and `yRange = low nibble`.
- Crusader multiplies each nibble by `64` world units and uses a `+/-48` Z window for the trigger test.
- `TRIGEGG` and `ONCEEGG` route into `TRIGGER.slot_20` on hatch/unhatch, so the renderer now draws local arrows to nearby `0x04B1` helpers by shared `QLo`.
- Regret `MHATCHER` scans nearby frame-0 `0x04D0` helpers whose `QLo` matches the egg id in `mapNum`, so the renderer now draws that local helper lane too.
- Regret `DOOREGG` scans nearby family-1 door objects whose `QLo` matches the egg id in `mapNum`, so the renderer now exposes that local door lane.
- `FLOOR1`, `CHANGER`, `MISS1*`, and `VIDEOEGG` remain subtype-aware in the tooltip and USECODE target, but they do not yet justify a generic local-arrow rule.
- Map-13 Remorse `CHANGER` example `fixed:4770` now gives the subtype a concrete local read: egg id `37` (`mapNum`) sits beside roof tiles whose `QLo` is also `37`, matching the extracted `CHANGER::hatch` body that destroys nearby roofs keyed by egg id.
- `FLOOR1`, `CHANGER`, `MISS1*`, and `VIDEOEGG` remain subtype-aware in the tooltip and USECODE target, but they still do not justify a generic local-arrow rule.
### `0x04C9 TIMER`