Crusader_Decomp/docs/map_renderer/egg-identification.md

145 lines
7 KiB
Markdown
Raw Normal View History

2026-03-30 00:19:01 +02:00
# 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.
### 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.
## 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