Crusader_Decomp/docs/map_renderer/egg-identification.md
2026-03-30 00:19:01 +02:00

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, 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 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:

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