786 lines
No EOL
44 KiB
Markdown
786 lines
No EOL
44 KiB
Markdown
# Egg Identification Investigation
|
|
|
|
## Goal
|
|
|
|
Add a reliable egg browser to the map renderer and clarify what the game means by an egg "ID".
|
|
|
|
## Short Answer
|
|
|
|
- Fresh-game startup is hardcoded to map `1`, egg `0x1e`.
|
|
- That startup egg is not read from `CRUSADER.CFG` or another external mission map.
|
|
- For Crusader teleport eggs, the destination number that gameplay matches against is the low byte of the item `quality` field.
|
|
- The broader egg families use different payload fields depending on egg type, so the renderer should not assume that every egg-family item uses the same number source.
|
|
|
|
## Fresh-Game Default Start Egg
|
|
|
|
The existing reverse-engineering note in [docs/first-mission-map-selection.md](k:/ghidra/Crusader_Decomp/docs/first-mission-map-selection.md) already establishes the startup path:
|
|
|
|
- normal new game calls `Teleporter_CreateProcessDirect(1, 0x1e, 1)`
|
|
- the controlling call site is in `Game_Start`
|
|
- this is a code-selected default, not a config-file mapping
|
|
|
|
That means the known first-map spawn uses teleport egg id `0x1e` on map `1`.
|
|
|
|
## ScummVM Crusader Egg Model
|
|
|
|
The ScummVM Crusader engine in `engines/ultima/ultima8` maps Crusader egg families as follows:
|
|
|
|
- family `3` = `SF_GLOBEGG`
|
|
- family `4` = `SF_UNKEGG` (usecode trigger egg)
|
|
- family `7` = `SF_MONSTEREGG`
|
|
- family `8` = `SF_TELEPORTEGG`
|
|
|
|
Relevant files:
|
|
|
|
- [shape_info.h](k:/misc/scummvm/engines/ultima/ultima8/gfx/shape_info.h)
|
|
- [item_factory.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/item_factory.cpp)
|
|
- [egg.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/egg.cpp)
|
|
- [egg.h](k:/misc/scummvm/engines/ultima/ultima8/world/egg.h)
|
|
- [monster_egg.h](k:/misc/scummvm/engines/ultima/ultima8/world/monster_egg.h)
|
|
- [monster_egg.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/monster_egg.cpp)
|
|
- [teleport_egg.h](k:/misc/scummvm/engines/ultima/ultima8/world/teleport_egg.h)
|
|
- [teleport_egg.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/teleport_egg.cpp)
|
|
- [current_map.cpp](k:/misc/scummvm/engines/ultima/ultima8/world/current_map.cpp)
|
|
|
|
One structural detail matters here: generic trigger eggs and teleport eggs inherit from `Egg`, but `MonsterEgg` is its own `Item` subclass instead of an `Egg` subclass. That helps explain why Crusader monster eggs do not line up perfectly with the generic egg-field conventions.
|
|
|
|
## Which Field Holds The Number?
|
|
|
|
There is not one universal answer for every egg-family item.
|
|
|
|
### Teleport eggs
|
|
|
|
Teleport eggs are the important case for spawn/start locations.
|
|
|
|
ScummVM uses:
|
|
|
|
- `TeleportEgg::getTeleportId()` = `(_quality & 0xFF)`
|
|
- `MainActor::teleport(mapNum, teleport_id)` to move to a destination egg
|
|
- `CurrentMap::findDestination(id)` to find a teleport egg target with matching low-byte quality
|
|
|
|
It also distinguishes two teleport egg roles:
|
|
|
|
- `frame != 1` = active teleporter trigger
|
|
- `frame == 1` = destination marker
|
|
|
|
So for renderer purposes, the gameplay-relevant teleport egg id is the low byte of `quality`, not `mapNum`.
|
|
|
|
### Generic/usecode eggs
|
|
|
|
ScummVM's generic egg intrinsics expose:
|
|
|
|
- `Egg::I_getEggId()` -> `getMapNum()`
|
|
|
|
So family `4` eggs use `mapNum` as their generic egg id in the engine interface.
|
|
|
|
Current Crusader viewer work now closes one additional family-4 detail for the `0x0011` placements used as usecode-trigger eggs:
|
|
|
|
- `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`
|
|
- 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`
|
|
- `yRange = npcNum & 0x0f`
|
|
- Crusader multiplies those range nibbles by `64` world units.
|
|
- The runtime trigger check also uses a `+/-48` Z window.
|
|
|
|
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.
|
|
|
|
### Monster eggs
|
|
|
|
ScummVM's monster egg accessor exposes:
|
|
|
|
- `MonsterEgg::I_getMonId()` -> `getMapNum() >> 3`
|
|
|
|
That is a separate meaning from teleport ids.
|
|
|
|
There is an important Crusader-specific wrinkle for renderer work:
|
|
|
|
- the generic ScummVM `MonsterEgg` model stores monster shape in `quality & 0x7FF` and activity in the low three bits of `mapNum`
|
|
- but exported No Remorse `0x024F` frame `0` monster eggs often have `quality = 0` and still carry non-zero `npcNum` values that line up with useful DTABLE rows
|
|
- concrete exported examples include Remorse map 69 (`npcNum = 2` and `npcNum = 6`) and Remorse map 2 (`rawNpcNum = 5`)
|
|
- No Remorse frame `1` `0x024F` entries also exist, but the checked cases were zeroed placeholders with no useful `npcNum`, `mapNum`, or `quality` payload
|
|
- exported No Regret `0x024F` currently diverges earlier: its shape definition resolves to `family: 0`, `kind: terrain` rather than `family: 7`, `kind: egg`, so the renderer should not assume that Remorse monster-egg semantics carry over unchanged
|
|
|
|
So the renderer keeps two interpretations side by side for family `7` eggs:
|
|
|
|
- the egg-family label remains `monster id = mapNum >> 3`, matching the engine intrinsic surface
|
|
- the NPC preview for evidence-backed `0x024F` frame `0` eggs comes from `npcNum`, because that field matches observed spawn identity better than the zero-heavy `quality` field in current Remorse exports
|
|
|
|
### Glob eggs
|
|
|
|
Glob eggs expand glob contents using the item `quality` value as the glob reference.
|
|
|
|
## Renderer Decision
|
|
|
|
The new map-renderer egg UI treats all egg-family items as eggs, but labels each one with the field that matches its ScummVM family behavior:
|
|
|
|
- family `8`: teleport id = `quality & 0xFF`
|
|
- family `4`: egg id = `mapNum`
|
|
- family `7`: monster id = `mapNum >> 3`
|
|
- family `3`: glob id = `quality`
|
|
|
|
That keeps the viewer useful for teleport/start-point work without flattening all egg families into one misleading scheme.
|
|
|
|
For the NPC preview overlay, the renderer now makes one additional evidence-backed exception:
|
|
|
|
- `0x024F` frame `0` monster eggs with a non-zero `npcNum` get the same DTABLE-backed blue NPC preview as `0x04D0` spawners
|
|
|
|
That exception is intentionally narrower than the generic egg browser labels. It reflects the currently verified Remorse data, not a blanket claim that every family `7` egg in every game uses `npcNum` as its authoritative spawn row.
|
|
|
|
## Current Cross-Game State Of `0x024F`
|
|
|
|
- No Remorse: exported shape definition resolves to `family: 7`, `kind: egg`, and scene items produce `egg.type = monster-spawn`
|
|
- No Regret: exported shape definition currently resolves to `family: 0`, `kind: terrain`, and scene items do not produce egg metadata
|
|
|
|
That is the strongest current reason to keep the new monster-egg preview support scoped to the evidence-backed Remorse path instead of enabling it globally for both games.
|
|
|
|
## Weapon Room `250`
|
|
|
|
The current renderer catalog data already contains egg-oriented notes that include `250` in the known egg-id lists for Crusader egg shapes, which is consistent with the user-observed convention that egg `250` is the weapons room/test room marker.
|
|
|
|
Those catalog references are evidence for the workflow and UI, but the renderer should still treat them as conventions layered on top of the raw egg-family decoding above.
|
|
|
|
## Non-Egg Teleporter Carriers
|
|
|
|
Not every authored teleporter in the exported maps is itself an egg-family item.
|
|
|
|
- Both retail games use shape `0x01DB` (`TELEPORTER_LIGHTS`) as a non-egg teleporter helper even though the exported shape definition is terrain-like rather than family `8` egg content.
|
|
- Current renderer scene exports show both frame `1` and some frame `0` `0x01DB` placements carrying small low-byte `quality` values that line up with real teleport-destination eggs.
|
|
- Verified No Remorse examples include map `13`, where frame `1` `0x01DB` items carry `quality & 0xFF = 14` and the map also contains teleport-destination egg `14`, and map `3`, where one frame `1` `0x01DB` carries low-byte `16` and the map contains teleport-destination egg `16`.
|
|
- Verified No Regret examples now include map `3` frame-`1` matches (`quality 4 -> destination egg 4`, `quality 2 -> destination egg 2`, `quality 9 -> destination egg 9`) plus the remaining frame-`0` telepad helpers (`quality 27 -> destination egg 27`, `quality 28 -> destination egg 28`), map `5` (`quality 78 -> destination egg 78`), map `10` (`quality 39 -> destination egg 39`, `quality 49 -> destination egg 49`), map `11` (`quality 19/20/21 -> destination eggs 19/20/21`), and map `15` (`quality 22 -> destination egg 22`).
|
|
|
|
That evidence is strong enough for link-arrow visualization, but it is intentionally narrower than the family `8` egg rule above:
|
|
|
|
- the renderer now treats frame `0` and frame `1` `0x01DB` placements as teleporter link sources for arrow rendering when they carry an integer destination id in `quality`
|
|
- the link key is the low byte of `quality`
|
|
- the destination side still comes from real teleport-destination eggs
|
|
- this does not yet claim that every `0x01DB` placement or every non-egg teleporter helper in Crusader uses the same schema
|
|
|
|
## TELEPAD Hardcoding
|
|
|
|
The current best executable-side explanation for `0x01DB` behavior comes from the checked Crusader usecode disassembly corpus, not from the generic ScummVM egg model.
|
|
|
|
- `Usecode class 475 (01DB TELEPAD)` is a real authored telepad class in `crusader_disasm.txt`.
|
|
- `TELEPAD::gotHit` reads the pad's `quality` into local `theQual` with `Item::I_getQuality`.
|
|
- `quality == 0xFF` is treated as inert and exits without a teleport.
|
|
- `quality` in the low range `1..0x1d` uses the actor's current map together with the quality value as the egg id, then spawns `TELEPAD::ordinal20` to perform the move.
|
|
|
|
That means the simple viewer rule for `0x01DB` is not arbitrary UI inference. It matches a real quality-driven telepad dispatch lane.
|
|
|
|
The same `TELEPAD::gotHit` body also contains explicit hardcoded remaps and special cases rather than one universal `quality -> same-map egg` rule:
|
|
|
|
- `0x1e -> map 0x28, egg 0x1e`
|
|
- `0x1f -> map 0x45, egg 0x45`
|
|
- `0x20 -> map 0x04, egg 0x10`
|
|
- `0x21 -> map 0x03, egg 0x11`
|
|
- `0x23 -> map 0x29, egg 0x1e`
|
|
- `0x64 -> map 0x05, egg 0x64`
|
|
- `0x65 -> map 0x05, egg 0x65`
|
|
- `0x82 -> map 0x19, egg 0x82`
|
|
- `0x83 -> map 0x19, egg 0x83`
|
|
- `0x84 -> map 0x19, egg 0x82`
|
|
- `0x85 -> map 0x19, egg 0x83`
|
|
- `0xd7/0xda/0xdb/0xdc -> map 0x05` with matching egg ids
|
|
|
|
`TELEPAD::ordinal20` then confirms the actual move mechanism:
|
|
|
|
- the normal lane calls `MainActor::I_teleportToEgg(...)`
|
|
- same-map and cross-map forms both appear
|
|
- map `0x28` and map `0x29` have extra hardcoded presentation/cutscene handling around the jump process, including `R01`, `B01`, `wec`, `1d`, and `14a` movie names
|
|
|
|
So the current safest renderer claim is:
|
|
|
|
- frame `0` and frame `1` `0x01DB` placements are useful cross-game teleporter-link sources when low-byte `quality` matches a real destination egg
|
|
- but the full authored gameplay semantics of `TELEPAD` include a hardcoded quality switch with special map-specific behavior, not just generic destination-egg matching
|
|
|
|
## Elevator Helpers
|
|
|
|
The checked usecode corpus also closes one separate same-map elevator lane used by visible elevator car objects rather than by egg-family source triggers.
|
|
|
|
- `Usecode class 542 (021E ELEVATOR)` reads `Item::I_getQLo` and `Item::I_getQHi` from the elevator object itself.
|
|
- For same-map moves, `ELEVATOR::gotHit` dispatches `ELEVATOR::ordinal20` with destination egg ids derived from the low quality byte.
|
|
- The verified same-map cases are `QLo 1..0x0f -> egg 1..0x0f` and the explicit special case `QLo 0x10 -> egg 4`.
|
|
- `QLo 0x11` and `QLo 0x12` are cross-map special cases in the checked body, so the viewer does not currently draw same-map arrows for those values.
|
|
- `ELEVATOR::ordinal20` later confirms that the move itself is still a `MainActor::I_teleportToEgg(...)` hop using the caller-supplied egg id and map.
|
|
|
|
Scene evidence lines up with that lane in No Remorse map `1`:
|
|
|
|
- `shape:542` quality `0x0101` sits near destination egg `2`, which matches a source platform that teleports to egg `1`.
|
|
- `shape:542` quality `0x0202` sits near destination egg `1`, which matches the return platform that teleports to egg `2`.
|
|
- the same authored pattern repeats for the other nearby elevator pairs, including `0x0105 -> egg 5` from the far endpoint and the local return platform `0x0206 -> egg 6` beside destination egg `5`.
|
|
|
|
So the public renderer now treats same-map `shape:542` frame-`0` placements as elevator-link sources when their checked `QLo` values map to a local destination egg id.
|
|
|
|
## Current Elevator Gap
|
|
|
|
- No Regret map `3` destination egg `102` does not sit on the same currently verified `ELEVATOR` (`shape:542`) or `LIFT` (`shape:307`) lanes.
|
|
- Current scene exports for that map show no nearby `shape:542` or `shape:307` objects at all.
|
|
- A nearby editor/helper record `item:1056:fixed:1201:0:41758:33694:0` carries `mapNum 102`, but there is not yet enough executable-side evidence to promote that into a viewer arrow rule.
|
|
- The map renderer therefore leaves egg `102` unresolved for now instead of hardcoding a speculative elevator source pattern.
|
|
|
|
## Adjacent Editor Objects: `0x04E3` And `0x04B1`
|
|
|
|
These two shapes came up during egg-browser work because they sit near teleporter/elevator/trap authoring patterns, but the current evidence says neither one should be folded into the egg model.
|
|
|
|
### `0x04E3` frame `0`: `SKILLBOX`
|
|
|
|
The recovered Crusader usecode corpus has a direct class label here:
|
|
|
|
- `Usecode class 1251 (04E3 SKILLBOX)`
|
|
- the recovered handler is `SKILLBOX::func0A(sint16)`
|
|
|
|
That body is difficulty-sensitive rather than egg-like.
|
|
|
|
- it reads `Game::I_getDifficultyLevel()`
|
|
- it branches on the object's `frame`
|
|
- for `frame == 2`, it reads `Item::I_getQLo(item)`, temporarily adjusts that low-quality byte by difficulty, dispatches `TRIGGER::ordinal20`, then restores the original `QLo`
|
|
- for the other frames, it chooses between trigger lane `0` and trigger lane `1` based on a difficulty threshold derived from `frame + 2`
|
|
- when `Item::I_getMapArray(item)` is nonzero, the same lane choice is made but with `0x80` added to the dispatched trigger selector
|
|
|
|
That gives the current best read of the family:
|
|
|
|
- `frame 0` = difficulty gate that flips at difficulty level `2`
|
|
- `frame 1` = difficulty gate that flips at difficulty level `3`
|
|
- `frame 2` = special skill-routing form that rewrites `QLo` per difficulty before dispatch
|
|
|
|
For the specific target in this pass, `0x04E3` frame `0` is therefore best read as a hidden difficulty gate or skill-route selector, not as a teleporter egg, generic egg id carrier, or NPC spawner.
|
|
|
|
Scene-cache evidence supports that split.
|
|
|
|
- across cached maps, `0x04E3` appears mostly as `frame 2`, then `frame 1`, with relatively few `frame 0` records (`64` in cached Remorse scenes and `25` in cached Regret scenes)
|
|
- `frame 2` payloads cluster strongly by `QLo`, which matches the recovered `Item::I_getQLo(...)` access
|
|
- `frame 0` payloads are much more heterogeneous, which fits a gate/controller role better than a simple numeric spawn id
|
|
- many dominant `frame 2` records reuse the same `npcNum/mapNum` pairs such as `208/134`, while the low byte of `quality` still varies meaningfully; that makes the low byte look like the authored control value and the other bytes look more like secondary metadata
|
|
|
|
What helps in the editor:
|
|
|
|
- label `0x04E3` as `SKILLBOX` instead of generic `Editor Object`
|
|
- show `quality` split into `QLo` and `QHi`
|
|
- for `frame 0` and `frame 1`, show a small derived note describing the difficulty switch point
|
|
- for `frame 2`, show the resolved difficulty lanes explicitly: `diff1 -> QLo`, `diff2 -> QLo + 1`, `diff3+ -> QLo + 2`
|
|
- keep `npcNum` and `mapNum` visible, but treat them as raw helper metadata rather than as DTABLE or egg ids
|
|
|
|
### `0x04B1` frame `0`: trigger/link controller
|
|
|
|
`0x04B1` still does not have as clean a recovered class label as `SKILLBOX`, but the current usecode evidence already points in one direction.
|
|
|
|
- an earlier trigger pass found `TRIGGER.slot_20` iterating nearby `shape=0x04B1` items
|
|
- that trigger lane compares `Item.getQLo(item)` against a base link id
|
|
- it then branches on `Item.getMapNum(item)` flag bits before dispatching additional trigger logic
|
|
|
|
This pass also re-confirmed that `0x04B1` is grouped with controller/helper shapes rather than with actor payload objects.
|
|
|
|
- recovered control code such as `ELEVATOR::gotHit` explicitly scans nearby authored helper shapes and includes `0x04B1` in that control-side whitelist
|
|
- that keeps `0x04B1` in the same ecosystem as trigger/elevator helper markers rather than DTABLE-driven spawn objects
|
|
|
|
The currently recovered `cmd` text is not yet a stable class name. It appears as a local variable name in recovered usecode exports, which fits a command/controller interpretation, but that is still weaker evidence than the `SKILLBOX` label above.
|
|
|
|
Scene-cache evidence strengthens the controller model.
|
|
|
|
- cached Remorse scenes overwhelmingly use `0x04B1` as `frame 0`
|
|
- cached Regret scenes use `0x04B1` across a much wider frame spread (`0` through `10` in the current cache)
|
|
- for Remorse `frame 0`, the most common low-byte values are small channel-like numbers such as `1`, `2`, `3`, `5`, `6`, `10`, `20`, `30`, `31`, `40`, `50`, and `60`
|
|
- the full `quality` word often changes while the low byte stays on the same small channel number, which is exactly what the earlier `Item.getQLo(...)` trigger evidence would predict
|
|
- the frequently repeated `npcNum/mapNum` pairs like `208/134` do not line up with a useful NPC-row interpretation and should stay raw for now
|
|
|
|
Current best read:
|
|
|
|
- `0x04B1` frame `0` is a command/link helper used by trigger-style controller logic
|
|
- `quality & 0xFF` is the strongest current candidate for the authored link or command id
|
|
- `mapNum` is not a destination map; its low bits are routing flags and its high bits contribute to the decoded target shape
|
|
- `npcNum` is not a DTABLE row here; it is the low byte of the decoded target selector used by `TRIGGER.slot_21`
|
|
|
|
The recovered TRIGGER body now gives a more concrete field split for `0x04B1` itself:
|
|
|
|
- `QLo` = current link id matched against the upstream trigger chain
|
|
- `QHi` = subcommand selector in the low three bits, plus a small argument in the upper bits
|
|
- `mapNum & 0x03` = command mode
|
|
- `mapNum & 0x04` = route into the item-targeting TRIGGER lanes instead of the NPC-triggering side
|
|
- `mapNum & 0x08` = phase gate (`set -> phase 0 / 0x80`, `clear -> phase 1 / 0x81`)
|
|
- `mapNum & 0x10` = low-priority/deferred execution bucket
|
|
- `((mapNum & 0xE0) * 8) + npcNum` = decoded target search shape or sentinel target code
|
|
|
|
The executable-side read is now concrete enough to say more than "it probably triggers something": several `QHi` subcommands are visibly specific.
|
|
|
|
- subcommand `0` is a helper-dispatch lane: it walks nearby `0x0476` helpers with the same `QLo` and forwards the upper-bit argument into `FREE.slot_30` using the helper's packed payload fields
|
|
- subcommand `1` is a direct target-mutation lane whose exact effect depends on mode: the recovered paths can write `QHi`, write `QLo`, call `equip`, set `frame`, or run a timed `TRIGGER.slot_22 -> DOOR.slot_21` pulse on matched targets
|
|
- subcommand `2` is no longer just a placeholder in the editor readout: in the direct item-targeting body it resolves to a frame-set lane using the upper-bit argument as the frame value
|
|
- subcommands `4` and `5` are verified link rewrites, adding to or subtracting from the active `QLo` before the controller continues scanning
|
|
- subcommand `6` is a create-and-drop lane: it again resolves payload through nearby `0x0476` helpers, creates the target item, copies `Q`, moves it to the helper coordinates, and unequips/drops it
|
|
|
|
The strongest new practical result is that some authored `0x04B1` records can now be resolved to concrete nearby target shapes instead of staying purely abstract.
|
|
|
|
- Remorse map `10` contains `0x04B1` items with decoded target shape `0x04D0`, and several of those have nearby same-`QLo` `0x04D0` helper matches.
|
|
- The same map also contains `0x04B1` items resolving to decoded target shapes `0x0476` and `0x04E3`, each with local same-`QLo` matches.
|
|
- Example authored records include `mapNum=134, npcNum=208 -> target shape 0x04D0`, `mapNum=135, npcNum=118 -> target shape 0x0476`, and `mapNum=134, npcNum=227 -> target shape 0x04E3`.
|
|
|
|
That still does not mean every `0x04B1` record is fully solved. Some target codes still decode to shapes that need more map-side correlation, and the NPC-triggering versus item-targeting paths do not use every subcommand in exactly the same way. But the object is now clearly more than a generic relay: it is a compact local trigger program that encodes phase, priority, target domain, target shape, link rewrites, and several concrete operation lanes in the standard item fields.
|
|
|
|
What helps in the editor:
|
|
|
|
- relabel `0x04B1` from generic `Editor Object` to something like `Trigger Link Controller` or `Cmd Link`
|
|
- show `QLo` and `QHi` separately, with `QLo` emphasized
|
|
- decode `mapNum` into phase lane, priority, targeting mode, and high-bit target-shape contribution instead of only showing it raw
|
|
- decode `npcNum` as the low byte of the target selector
|
|
- show the derived target shape or sentinel family code directly in the tooltip
|
|
- list nearby same-`QLo` exact-shape candidates when the decoded target shape can be resolved in the current map
|
|
- do not attach DTABLE/NPC preview logic to this family
|
|
- keep a same-`QLo` highlight/filter action, because that remains the strongest local linkage field even after the target-shape decode
|
|
|
|
The practical egg-browser outcome is that both shapes are adjacent to egg workflows, but neither one should currently be decoded as an egg-family object. `0x04E3` looks like a difficulty/skill gate, and `0x04B1` looks like a trigger-link controller.
|
|
|
|
## More Editor Helpers
|
|
|
|
The next helper batch follows the same pattern: these are controller-side authored objects that sit near eggs, doors, hazards, or alarm setups, but they should stay in their own helper bucket instead of being collapsed into one generic editor-object schema.
|
|
|
|
### `0x0361` frame `0`: `EVENT`
|
|
|
|
This one now has a direct recovered class label too.
|
|
|
|
- `Usecode class 865 (0361 EVENT)`
|
|
- recovered body: `EVENT::equip`
|
|
|
|
`EVENT::equip` is a large event multiplexer rather than a single-purpose marker.
|
|
|
|
- it immediately reads `link = Item.getQLo(arg_06)`
|
|
- it branches on the incoming `event` number
|
|
- several branches dispatch `TRIGGER.slot_20`
|
|
- other branches touch camera state, audio, NPC actions, doors, and nearby `NUMBERS` helpers
|
|
|
|
Current best read:
|
|
|
|
- `0x0361` is a generic scripted event controller/helper
|
|
- `QLo` is the strongest authored linkage field currently visible in the recovered body
|
|
- `QHi` is used by at least some counter-style branches, so it is worth surfacing too
|
|
- `frame 0` should be treated as one authored placement of that broader event family, not as a special egg/NPC record
|
|
|
|
What helps in the editor:
|
|
|
|
- override placeholder-style catalog names with `EVENT`
|
|
- emphasize `QLo` and `QHi`
|
|
- describe it as a generic event controller, not a visible prop
|
|
|
|
### `0x0500` frame `0`: `STEAMBOX`
|
|
|
|
This helper also has a direct class label.
|
|
|
|
- `Usecode class 1280 (0500 STEAMBOX)`
|
|
- recovered body: `STEAMBOX::equip`
|
|
|
|
The recovered handler has two visible controller lanes.
|
|
|
|
- for `event == 0`, it scans nearby steam-family shapes and matches them by `Item.getQLo(...)`
|
|
- for `event == 1`, it scans nearby `shape=0x03A9` items, again matched by `QLo`
|
|
- matching items then dispatch into `STEAMBOX.slot_20` or `STEAMBOX.slot_21`
|
|
|
|
Current best read:
|
|
|
|
- `0x0500` is a steam hazard/control relay, not a generic placeholder cube
|
|
- `QLo` is the authored channel/link id
|
|
- `frame` matters on nearby controlled shapes more than on the controller itself
|
|
|
|
What helps in the editor:
|
|
|
|
- label it `STEAMBOX`
|
|
- show `QLo/QHi` with `QLo` emphasized as the steam channel
|
|
- keep the role text focused on nearby steam/hazard linkage
|
|
|
|
### `0x0561` frame `0`: `ALARMHAT`
|
|
|
|
This family was already partially documented in the dedicated alarm note, and the extracted body matches that earlier read.
|
|
|
|
- `Usecode class 1377 (0561 ALARMHAT)`
|
|
- recovered body: `ALARMHAT::equip`
|
|
|
|
The visible structure is local alarm-state control.
|
|
|
|
- `frame 0` scans nearby `shape=0x04D0` helpers and targets their `frame 0` state
|
|
- nonzero frames gate on `Item.isOnScreen(arg_06)` and nearby actor-family presence before scanning the same `0x04D0` family
|
|
|
|
Current best read:
|
|
|
|
- `0x0561` is a local alarm-state driver
|
|
- it is meant to flip or arm nearby `0x04D0` controller/spawner objects
|
|
- `frame 0` is the simple always-check local alarm form; nonzero frames add extra activation conditions
|
|
|
|
What helps in the editor:
|
|
|
|
- label it `ALARMHAT`
|
|
- add a frame note calling out `frame 0` as the direct local alarm lane
|
|
- keep `npcNum`/`mapNum` raw instead of forcing a DTABLE interpretation onto this helper itself
|
|
|
|
### `0x0581` frame `0`: `ALRMTRIG`
|
|
|
|
This one is compact and comparatively clean.
|
|
|
|
- `Usecode class 1409 (0581 ALRMTRIG)`
|
|
- recovered body: `ALRMTRIG::equip`
|
|
|
|
The whole handler is an alert-state relay.
|
|
|
|
- it checks `Item.getMapArray(arg_06)`
|
|
- it checks `World.getAlertActive()`
|
|
- it dispatches `TRIGGER.slot_20` with lane `0`, `1`, `0x80`, or `0x81`
|
|
|
|
Current best read:
|
|
|
|
- `0x0581` is an alert/alarm trigger relay
|
|
- the important authored split is `mapArray == 0` versus nonzero
|
|
- the second split is world alert state inactive versus active
|
|
|
|
What helps in the editor:
|
|
|
|
- label it `ALRMTRIG`
|
|
- show the `mapNum`/`mapArray` byte in hex because it behaves like a flag lane selector here
|
|
- describe the four resulting trigger lanes explicitly
|
|
|
|
### `0x04F8` frame `0`: destroyable-door helper
|
|
|
|
This shape still lacks a clean No Remorse class label in the recovered tables, but the door-side behavior is already concrete enough to expose in the editor.
|
|
|
|
- `DOOR.slot_23` iterates nearby `shape=0x04F8` items after the door damage path
|
|
- it matches them by `Item.getQLo(deathBox) == Item.getQLo(arg_06)`
|
|
- it dispatches `TRIGGER.slot_20` on lane `0` when `Item.getMapArray(deathBox) == 0`
|
|
- otherwise it dispatches the `0x80`-offset lane
|
|
|
|
Current best read:
|
|
|
|
- `0x04F8` is a door-side helper that lets authored doors become destroyable and then forward into trigger logic
|
|
- `QLo` is the local door/link key
|
|
- the map-array byte chooses the normal trigger lane versus the `+0x80` variant
|
|
|
|
That is strong enough to support an editor-facing role hint even though the final class/name provenance is still incomplete.
|
|
|
|
### `shape_04e3` / `0123only`
|
|
|
|
The workspace did not yield an exact `0123only` string hit in the current source, catalogs, or extracted usecode artifacts.
|
|
|
|
Current safest read:
|
|
|
|
- there is no evidence yet that `0123only` is a separate helper family from `0x04E3`
|
|
- the only strong recovered identity for `shape 0x04E3` is still `SKILLBOX`
|
|
- treat `0123only` as an external nickname or pending label until a real source hit turns up
|
|
|
|
So the editor integration should continue to show `SKILLBOX` for `0x04E3` and avoid promoting `0123only` to a canonical name.
|
|
|
|
### `0x0120`: `FASTSKIL`
|
|
|
|
This shape is a second skill-family controller, but it is not the same object as `SKILLBOX`.
|
|
|
|
- `Usecode class 288 (0120 FASTSKIL)`
|
|
- recovered handler: `FASTSKIL::enterFastArea()`
|
|
|
|
The recovered body is short enough to read directly.
|
|
|
|
- it waits `5` ticks before doing anything
|
|
- it only runs the main lane while `Item::I_getMapArray(item) == 0`
|
|
- for `frame == 2`, it reads the current `QLo` as a base skill/link id and dispatches `TRIGGER::ordinal20` lane `0` through three difficulty lanes: `diff1 -> QLo`, `diff2 -> QLo + 1`, `diff3+ -> QLo + 2`
|
|
- for the non-`frame 2` forms, it does not rewrite the link id; instead it compares difficulty against `frame + 2` and dispatches `TRIGGER::ordinal20` lane `0` below that threshold and lane `1` at or above it
|
|
|
|
That makes the current best read:
|
|
|
|
- `0x0120` is a fast-area skill gate/controller, not a generic editor cube
|
|
- `frame 0` flips at difficulty `2` (`diff1 -> lane 0`, `diff2+ -> lane 1`)
|
|
- `frame 1` flips at difficulty `3` (`diff1/2 -> lane 0`, `diff3+ -> lane 1`)
|
|
- `frame 2` is the explicit skill-routing form that remaps the downstream trigger `QLo` by `+0/+1/+2`
|
|
|
|
For editor arrows, the strongest conservative rule is the same local controller rule already used for other trigger sources, but with the frame-`2` rewrite made explicit.
|
|
|
|
- `FASTSKIL` can point to nearby `0x04B1` controller helpers when they live in the same local trigger cluster
|
|
- for `frame 0` and `frame 1`, the relevant local link key is the source item's current `QLo`
|
|
- for `frame 2`, the relevant local link keys are `QLo`, `QLo + 1`, and `QLo + 2`, matching the recovered difficulty lanes
|
|
- one concrete Remorse map `13` example is especially strong: a `FASTSKIL` placement and a `0x04B1` controller helper sit at the same coordinates `(35390, 20894)` with matching raw quality `283` / `QLo 27`
|
|
|
|
This still needs to stay conservative.
|
|
|
|
- broader scene sweeps do not justify treating every nearby `0x04B1` as a `FASTSKIL` target
|
|
- the renderer should therefore stay on the existing local-distance plus explicit `QLo` lane rule instead of inventing a wider object-class relationship
|
|
- the tooltip should explain the frame-specific trigger lane or `QLo` rewrite directly so the object reads as a trigger program rather than as another anonymous skill box
|
|
|
|
## Switch And Helper Follow-Up
|
|
|
|
This pass tightened several of the still-generic switch/helper shapes that sit next to egg workflows.
|
|
|
|
### `0x00A1` frame `0`: `PANELNS`
|
|
|
|
This one now has a direct recovered class label.
|
|
|
|
- `Usecode class 161 (00A1 PANELNS)`
|
|
- recovered handler: `PANELNS::use()`
|
|
|
|
The body is small and consistent with a plain directional wall panel switch.
|
|
|
|
- if `frame == 0`, the handler returns immediately
|
|
- if `Item::I_getMapArray(item) != 0`, the handler also exits without firing
|
|
- otherwise it plays SFX `0xAC` and dispatches `TRIGGER::ordinal20` with lane `0`
|
|
|
|
Current best read:
|
|
|
|
- `0x00A1` frame `0` is the idle/inactive visual state of a panel switch family
|
|
- the important authored controller key is still downstream `QLo`, because the spawned `TRIGGER` path uses that link namespace rather than `mapNum` or `npcNum`
|
|
- sampled Remorse scene caches show nearby same-`QLo` `0x04B1` controller helpers often enough to support editor arrows as a local-assistance layer
|
|
|
|
### `0x031D` frame `0`: `CARD_NS`
|
|
|
|
This one also closes cleanly from the recovered usecode corpus.
|
|
|
|
- `Usecode class 797 (031D CARD_NS)`
|
|
- recovered handler: `CARD_NS::use()`
|
|
|
|
`CARD_NS::use()` is only a thin wrapper. It immediately spawns `SWITCH::ordinal21` on the source item.
|
|
|
|
`SWITCH::ordinal21` is the more useful body for editor work.
|
|
|
|
- it reads `Item::I_getQLo(item)` and passes that to `MainActor::I_hasKeycard(...)`
|
|
- if the switch is already in `frame 4`, it routes straight into `TRIGGER::ordinal20` lane `0`
|
|
- otherwise, with the right card and no alert-state block, it flips to `frame 4`, plays the authorization SFX lane, then dispatches `TRIGGER::ordinal20` lane `0`
|
|
- the denied path eventually dispatches `TRIGGER::ordinal20` lane `1`
|
|
|
|
Current best read:
|
|
|
|
- `0x031D` frame `0` is a locked north/south keycard switch
|
|
- `QLo` is the keycard id and the strongest local link field for controller arrows
|
|
- same-map same-`QLo` `0x04B1` matches appear in sampled Remorse scenes, so the renderer can expose them as editor-helper arrows without claiming that the switch talks directly to those helpers in one hardcoded object-only lane
|
|
|
|
### `0x03AA` frame `0`: `SPANEL`
|
|
|
|
This shape now has both a direct class label and a second helper-side link path.
|
|
|
|
- `Usecode class 938 (03AA SPANEL)`
|
|
- recovered handler: `SPANEL::use()`
|
|
|
|
The direct switch body is simple.
|
|
|
|
- it exits when `mapArray != 0`
|
|
- otherwise it conditionally plays SFX `0xAF` for the early-map lane
|
|
- it then dispatches `TRIGGER::ordinal20` lane `0`
|
|
|
|
That closes the same switch-style controller story as `PANELNS`, with `QLo` remaining the practical local link key through the downstream `TRIGGER` chain.
|
|
|
|
`SPANEL` also appears on the target side of another helper family.
|
|
|
|
- `BRO_BOOT::enterFastArea()` scans nearby `shape 0x03AA` items
|
|
- it compares them by shared `QLo`
|
|
- depending on the active mission/global branch, it applies `ITEM::ordinal23` or `ITEM::ordinal24` to those matching `SPANEL` items
|
|
|
|
Current best read:
|
|
|
|
- `0x03AA` frame `0` is a switch panel source in the trigger/controller ecosystem
|
|
- it is also a helper target for `BRO_BOOT`
|
|
- broader exported-scene sweeps now show repeated local same-`QLo` `BRO_BOOT -> SPANEL` matches on Remorse maps `9`, `10`, `11`, `21`, `23`, `160`, and `246`, so the renderer can now expose that helper lane conservatively too
|
|
|
|
### `0x0366` frame `0`: `NPC_ONLY`
|
|
|
|
The earlier placeholder label is now closed.
|
|
|
|
- `Usecode class 870 (0366 NPC_ONLY)`
|
|
- recovered handler: `NPC_ONLY::gotHit(uword, word)`
|
|
|
|
The body is a gated trigger pad rather than a generic editor helper.
|
|
|
|
- it rejects the hit unless the incoming actor/reference resolves to an NPC-like source
|
|
- it reads `Actor::I_GetNPCDataField0x63_00B(actor)`
|
|
- it reads `Item::I_getQLo(item)` from the pad and requires those values to match
|
|
- with `mapArray == 0` and the occupancy checks satisfied, it flips to `frame 1`, waits briefly, and dispatches `TRIGGER::ordinal20` lane `0`
|
|
- once the actor is no longer satisfying the occupancy lane, it restores `frame 0` and dispatches `TRIGGER::ordinal20` lane `1`
|
|
|
|
Current best read:
|
|
|
|
- `0x0366` frame `0` is an idle NPC-only trigger pad
|
|
- `QLo` is the author-selected NPC-group key, but the executable compares it against actor field `0x63`, not against the scene-export `npcNum` or a simple DTABLE row
|
|
- current scene exports do not expose that actor-side field directly, so there is not yet enough evidence to draw trustworthy `NPC_ONLY -> actor` arrows in the renderer
|
|
- a follow-up scene-cache sweep also failed to produce a convincing generic `NPC_ONLY -> 0x04B1` helper pattern; shared-`QLo` local matches look incidental rather than like a dedicated authored link lane
|
|
|
|
That means the safe editor improvement here is naming and field emphasis, not a target-arrow rule yet.
|
|
|
|
### `0x0403` frame `0`: `FLAMEBOX`
|
|
|
|
This shape now has a direct class label and a usable local arrow rule.
|
|
|
|
- `Usecode class 1027 (0403 FLAMEBOX)`
|
|
- recovered handler: `FLAMEBOX::equip(sint16)`
|
|
|
|
The recovered body has two nearby-helper lanes keyed by shared `QLo`.
|
|
|
|
- event `0` scans nearby flame-family shapes `0x043A`, `0x043B`, `0x050A`, and `0x0518`
|
|
- event `1` scans nearby flame editor/helper shapes `0x0438` and `0x0439`
|
|
- both lanes compare helper items by `Item::I_getQLo(...) == Item::I_getQLo(flamebox)`
|
|
- the event-`1` lane can replace the helper marker with an animated flame actor and then spawns the flame process
|
|
|
|
Scene-cache checks on Remorse map `1` line up with that lane strongly enough to expose in the editor.
|
|
|
|
- `FLAMEBOX qlo 24` at `(61310,60830)` has nearby helper `shape 0x0438` at `(61886,60798)`
|
|
- `FLAMEBOX qlo 7` at `(60350,49406)` has nearby helper `shape 0x050A` at `(59938,49294)`
|
|
- `FLAMEBOX qlo 21` at `(59166,5214)` has nearby `shape 0x0439` matches at `(58814,5790)` and `(59166,5790)`
|
|
|
|
Current best read:
|
|
|
|
- `0x0403` frame `0` is a local flame controller
|
|
- `QLo` is the authored flame channel id
|
|
- editor-helper arrows from `FLAMEBOX` to nearby same-`QLo` flame helper shapes are evidence-backed and now justified
|
|
|
|
### `0x04E7` frame `0`: `DEATHBOX`
|
|
|
|
The existing `npc death` nickname was directionally right but incomplete. The recovered class name and caller path now narrow the object down.
|
|
|
|
- `Usecode class 1255 (04E7 DEATHBOX)`
|
|
- recovered helper body: `DEATHBOX::func0A(sint16)`
|
|
- recovered caller path: `NPCDEATH::ordinal20` scans nearby `shape 0x04E7` items directly
|
|
|
|
The strong executable-side lane is in `NPCDEATH::ordinal20`.
|
|
|
|
- it iterates nearby `DEATHBOX` items
|
|
- it compares the incoming death-link value against `Item::I_getQLo(deathBox)`
|
|
- if `Item::I_getMapArray(deathBox) == 0`, it dispatches `TRIGGER::ordinal20` lane `0` from the helper item
|
|
- otherwise it reads `QHi` and `npcNum` from the helper, can create a keycard with frame `QHi - 1`, can override that keycard's `QLo` from helper `npcNum`, and then dispatches the `0x80` trigger lane
|
|
|
|
That makes the current best editor-facing read:
|
|
|
|
- `0x04E7` is an `NPCDEATH` helper/controller, not just a raw death marker
|
|
- `QLo` is the death-link match key
|
|
- `QHi` carries drop/helper mode information, including the verified keycard lane for values `1..4`
|
|
- `npcNum` can act as a secondary payload for the spawned keycard helper path
|
|
|
|
Current limitation:
|
|
|
|
- the static scene export does not yet expose the originating NPC death-link values directly enough to draw reliable `DEATHBOX -> source NPC` arrows
|
|
- some maps do show local same-`QLo` `0x04B1` helpers near `DEATHBOX`, but the recovered executable path is still clearest as `NPC death event -> DEATHBOX -> TRIGGER`, and a broader follow-up sweep did not justify promoting `DEATHBOX -> 0x04B1` into a generic arrow rule
|
|
|
|
### `0x04FE` frame `0` / frame `1`: `BRO_BOOT`
|
|
|
|
This shape is no longer mysterious, even though its local authored targets are not yet consistently visible in sampled scenes.
|
|
|
|
- recovered class label: `BRO_BOOT`
|
|
- recovered handlers include `BRO_BOOT::enterFastArea()` and `BRO_BOOT::leaveFastArea()`
|
|
|
|
`BRO_BOOT::enterFastArea()` does three important things.
|
|
|
|
- it branches on a mission/global state lane (`global [001F 01]` in the recovered output)
|
|
- it scans nearby `shape 0x03AA` items and compares them by shared `QLo`
|
|
- it applies `ITEM::ordinal23` or `ITEM::ordinal24` to those matching `SPANEL` items, then runs its own boot-style frame animation loop
|
|
|
|
The broader scene cache now supports that link well enough for a renderer overlay.
|
|
|
|
- follow-up exported-scene sweeps found repeated local same-`QLo` `BRO_BOOT -> SPANEL` matches across Remorse maps `9`, `10`, `11`, `21`, `23`, `160`, and `246`
|
|
- one concrete Remorse map `10` example has `SPANEL` item `4538` at `(21018, 13406)` with a nearby `BRO_BOOT` at `(20968, 13784)`, both carrying `QLo 50`
|
|
|
|
Current best read:
|
|
|
|
- `0x04FE` is a scripted helper tied to nearby `SPANEL` items, not a free-form generic editor object
|
|
- frame `0` and frame `1` are boot-sequence animation states, not independent target ids
|
|
- `QLo` is the local authored linkage field for the helper-to-`SPANEL` lane
|
|
|
|
Current renderer stance:
|
|
|
|
- this is strong enough to relabel the family as `BRO_BOOT`
|
|
- the exported-scene sweep is now broad enough to justify a generic local `BRO_BOOT -> SPANEL` arrow layer based on shared `QLo` plus local distance
|
|
|
|
### `0x0080` frame `0`: `BOX_EW`
|
|
|
|
This one closes as another switch-family trigger source.
|
|
|
|
- `Usecode class 128 (0080 BOX_EW)`
|
|
- recovered handler: `BOX_EW::use()`
|
|
|
|
The recovered body is compact but useful.
|
|
|
|
- if `mapArray != 0`, it exits without firing
|
|
- `frame 0` plays the switch SFX and dispatches `TRIGGER::ordinal20` lane `1`
|
|
- nonzero frames dispatch `TRIGGER::ordinal20` lane `0`
|
|
|
|
The map-side correlation is strong enough to promote a conservative helper-arrow rule, but only for the idle switch state.
|
|
|
|
- sampled exported scenes show repeated local same-`QLo` `BOX_EW frame 0 -> 0x04B1` matches, including co-located pairs on Remorse map `9`
|
|
- the same broader sweep did not justify treating nonzero `BOX_EW` frames as the same generic cmd-link source lane
|
|
|
|
Current best read:
|
|
|
|
- `0x0080` is the `BOX_EW` switch family
|
|
- `QLo` remains the practical authored controller key for nearby helper scans
|
|
- the renderer can safely expose `BOX_EW frame 0 -> nearby 0x04B1` arrows when local distance and `QLo` both match
|
|
|
|
### `0x04CD` frame `0`: `TRIGPAD`
|
|
|
|
This family now has a direct class label, but not a new helper-arrow rule.
|
|
|
|
- `Usecode class 1229 (04CD TRIGPAD)`
|
|
- recovered handler: `TRIGPAD::gotHit(...)`
|
|
|
|
The recovered body behaves like a heavier floor trigger.
|
|
|
|
- it gates on occupancy and surface checks before the pad arms
|
|
- with `mapArray == 0`, it waits briefly, dispatches `TRIGGER::ordinal20` lane `0`, and later dispatches lane `1` as the condition clears
|
|
- the same body also loops across nearby elevator-family items and can call `ELEVAT` control slots in that path
|
|
|
|
Current best read:
|
|
|
|
- `0x04CD` is a real trigger-pad class, not a generic editor placeholder
|
|
- it belongs in the trigger/helper ecosystem, but its executable behavior is broader than a simple `QLo -> cmd-link` switch
|
|
- the wider exported-scene sweep did not justify promoting a generic local `TRIGPAD -> 0x04B1` arrow rule, so the safe editor improvement is labeling plus tooltip decoding only
|
|
|
|
### `0x033A` frame `0`: `NUMBERS`
|
|
|
|
This family still does not have a cleaner recovered usecode class label than the catalog-facing name, but the scene role is much less ambiguous now.
|
|
|
|
- exported scenes show `0x033A` as tiny glyph-sized frames, typically `10x16` or `12x16`
|
|
- those items repeatedly cluster beside larger sibling shapes such as `0x0501`, `0x0502`, `0x0503`, `0x0505`, and `0x0507`
|
|
- the same map-side checks do not make it look like another trigger or cmd-link helper family
|
|
|
|
Current best read:
|
|
|
|
- `0x033A` is best treated as a number/readout display helper family
|
|
- the renderer should label it clearly so editor users can spot display clusters
|
|
- current evidence does not justify helper arrows from `NUMBERS`
|
|
|
|
## Editor Helper Arrow Overlay
|
|
|
|
The renderer now exposes a separate nested toggle under `Show editor-only elements` called `Show editor helper arrows`.
|
|
|
|
That overlay is intentionally narrower than the existing verified teleport/elevator arrow layer. It only draws local helper relationships that have direct usecode support and an evidence-backed authored key:
|
|
|
|
- `ALARMHAT (0x0561) -> nearby 0x04D0` helpers, using the recovered local alarm scan behavior
|
|
- `STEAMBOX (0x0500) -> nearby steam-family targets`, matched by shared `QLo`
|
|
- `0x04F8 -> nearby destroyable door-family targets`, matched by shared `QLo` and local placement
|
|
- `BRO_BOOT (0x04FE) -> nearby SPANEL targets`, matched by shared `QLo`
|
|
- `PANELNS (0x00A1)`, `CARD_NS (0x031D)`, and `SPANEL (0x03AA) -> nearby 0x04B1` controller helpers when they share the same local `QLo`
|
|
- `BOX_EW (0x0080) frame 0 -> nearby 0x04B1` controller helpers when they share the same local `QLo`
|
|
- `FLAMEBOX (0x0403) -> nearby flame helper shapes`, matched by shared `QLo`
|
|
- `EVENT (0x0361)` and `SKILLBOX (0x04E3) -> nearby 0x04B1` controller helpers when they share the same local `QLo`
|
|
- `FASTSKIL (0x0120) -> nearby 0x04B1` controller helpers on the same local trigger lanes, with `frame 2` also exposing the recovered `QLo + 1` and `QLo + 2` difficulty variants
|
|
- `ALRMTRIG (0x0581) -> nearby 0x04B1` controller helpers by the same lane byte, as a weaker relay-style visualization rather than a claimed final object identity
|
|
- `0x04B1 -> nearby exact decoded target shapes` when the TRIGGER field split resolves to a concrete target shape and local same-`QLo` candidates exist
|
|
|
|
Two newly decoded families stay intentionally out of the arrow layer for now.
|
|
|
|
- `TRIGPAD (0x04CD)` is now named and decoded in tooltips, but the broader scene sweep did not justify a generic `TRIGPAD -> 0x04B1` helper rule
|
|
- `NUMBERS (0x033A)` looks like a display/readout marker family rather than a trigger/controller source, so it stays label-only
|
|
|
|
The important constraint is that these arrows are editor-assistance overlays, not canonical gameplay graphs. They are meant to expose likely local controller lanes without pretending that every helper object participates in one global id namespace.
|
|
|
|
## Live Ghidra Notes
|
|
|
|
The live `CRUSADER.EXE` Ghidra database now also has comment-backed notes at `1020:029e` and `1020:02d0` documenting the startup teleporter precedence that matters for map/egg overrides:
|
|
|
|
- if the X override remains `-1`, startup stays on the teleporter/egg path
|
|
- even when X/Y/Z coordinates are present, a nonnegative `-egg` override still wins and routes through `Teleporter_CreateProcessDirect`
|
|
|
|
That executable-side note is adjacent to the broader teleporter system rather than to `0x01DB` specifically, but it closes one important runtime rule for interpreting teleport-id driven startup behavior.
|
|
|
|
## Outcome For The Feature
|
|
|
|
The egg browser added to the renderer now:
|
|
|
|
- lists every egg-family item in the loaded map
|
|
- shows the decoded id appropriate to that egg family
|
|
- can center the camera on a chosen egg and pin-select it
|
|
- can draw zoom-stable id labels over eggs in the viewport
|
|
|
|
## Remaining Uncertainty
|
|
|
|
Two things are still worth keeping in mind:
|
|
|
|
- family `7` Crusader handling is still marked in ScummVM as partly suspicious because those records can also behave container-like
|
|
- `250 = weapons room` is a strong workflow convention, but this investigation did not add a new executable-side proof beyond the catalog evidence and known reverse-engineering notes |