7 KiB
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, egg0x1e. - That startup egg is not read from
CRUSADER.CFGor another external mission map. - For Crusader teleport eggs, the destination number that gameplay matches against is the low byte of the item
qualityfield. - 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 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
- item_factory.cpp
- egg.cpp
- egg.h
- monster_egg.h
- monster_egg.cpp
- teleport_egg.h
- teleport_egg.cpp
- 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 eggCurrentMap::findDestination(id)to find a teleport egg target with matching low-byte quality
It also distinguishes two teleport egg roles:
frame != 1= active teleporter triggerframe == 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
MonsterEggmodel stores monster shape inquality & 0x7FFand activity in the low three bits ofmapNum - but exported No Remorse
0x024Fframe0monster eggs often havequality = 0and still carry non-zeronpcNumvalues that line up with useful DTABLE rows - concrete exported examples include Remorse map 69 (
npcNum = 2andnpcNum = 6) and Remorse map 2 (rawNpcNum = 5) - No Remorse frame
10x024Fentries also exist, but the checked cases were zeroed placeholders with no usefulnpcNum,mapNum, orqualitypayload - exported No Regret
0x024Fcurrently diverges earlier: its shape definition resolves tofamily: 0,kind: terrainrather thanfamily: 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
0x024Fframe0eggs comes fromnpcNum, because that field matches observed spawn identity better than the zero-heavyqualityfield 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:
0x024Fframe0monster eggs with a non-zeronpcNumget the same DTABLE-backed blue NPC preview as0x04D0spawners
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 produceegg.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
7Crusader handling is still marked in ScummVM as partly suspicious because those records can also behave container-like 250 = weapons roomis a strong workflow convention, but this investigation did not add a new executable-side proof beyond the catalog evidence and known reverse-engineering notes