Skip to content

Creature spawning (creature_spawn_template / 0x00430af0)

creature_spawn_template(template_id, pos_xy, heading) is the primary translation layer from "spawn ids" (quests, tutorial timelines, and other scripted spawners) to initialized creature_t entries.

It is not a static struct/table: it always performs base creature initialization, runs a large template switch, may allocate additional creatures and/or spawn-slot entries, and then applies shared tail modifiers (difficulty/hardcore, demo gating, etc).

The port (creature_spawn_template in src/crimson/creatures/spawn.py) mirrors that shape: it writes each creature straight into the slot the allocator hands out, in native order, so fields a template leaves alone keep the recycled slot's values.

Spawn id name provenance

The canonical spawn-id names combine the Windows template_id values with developer-facing creature names recovered from the PS4 1.00 remake's data/creatures/creature-variants.xml.

The remake's legacy_variant_index is not the Windows spawn id. We cross-mapped fixed creatures by type and the exact health, speed, reward, damage, and size fingerprint (remake scale * 64), then retained the native Windows value as each identifier's hexadecimal suffix:

Windows id Canonical name Remake variant
0x0f ALIEN_GHOST_0F AlienGhost
0x21 ALIEN_HIDDEN_1_21 AlienHidden1
0x22 ALIEN_HIDDEN_2_22 AlienHidden2
0x23 ALIEN_HIDDEN_3_23 AlienHidden3
0x25 ALIEN_SMALL_GREEN_MAN_25 AlienSmallGreenMan
0x26 ALIEN_SMALL_GRAY_26 AlienSmallGray
0x27 ALIEN_BONUS_CARRIER_27 AlienBonusCarrier
0x29 ALIEN_BIG_GRAY_29 AlienBigGray
0x2b ALIEN_DEADLY_FAST_2B AlienDeadlyFast
0x3a SPIDER_BOSS_3A SpiderBoss
0x3c SPIDER_PLASMA_SHOOTER_3C SpiderPlasmaShooter
0x40 SPIDER_SMALL_BLUE_40 SpiderSmallBlue
0x42 ZOMBIE_SMALL_WHITE_42 ZombieSmallWhite

Spawner names are cross-mapped by their den family, child variant, spawn interval, and surviving stat fingerprints. Some balance fields changed in the remake, so these are semantic names rather than a claim that the records are byte-for-byte identical:

Windows id Canonical name Remake variant
0x07 DEN_ALIEN_BASIC_07 DenAlienBasic
0x08 DEN_ALIEN_BASIC_SLOWER_08 DenAlienBasicSlower
0x09 DEN_ALIEN_WEAK_SMALL_09 DenAlienWeakSmall
0x0a DEN_SPIDER_BASIC_0A DenSpiderBasic
0x0b DEN_SPIDER_PLASMA_SHOOTERS_0B DenSpiderPlasmaShooters
0x0c DEN_LIZARD_WEAK_0C DenLizardWeak
0x0d DEN_LIZARD_WEAK_SLOWER_0D DenLizardWeakSlower
0x10 DEN_SPIDER_WEAK_10 DenSpiderWeak

Inputs

  • template_id (aka param_1): spawn id used by quest tables and other spawners.
  • pos_xy (aka param_2): spawn position (two floats).
  • heading (aka param_3): radians; sentinel -100.0 means "randomize heading".
  • Globals consulted:
  • RNG stream: crt_rand() (MSVCRT rand()).
  • demo_mode_active: skips the spawn burst effect when nonzero.
  • terrain_texture_width/terrain_texture_height: bounds check for the burst effect.
  • config_blob.hardcore and quest_fail_retry_count (difficulty level): final stat modifiers.

Outputs and side effects

  • Returns a pointer into creature_pool (creature_t *). Some templates return the last creature allocated (for formations), not necessarily the base creature.

  • May allocate additional creature_pool entries (formation spawns, escorts).

  • May allocate and configure spawn-slot entries (deferred child spawns driven by creature_update_all).
  • May spawn a burst effect at the spawn position (skipped in demo mode or when out of bounds).

Grid templates 0x14..0x18 allocate one root plus 27 children: nine columns at x offsets 0, -64, ..., -512, each with y offsets 128, 192, 256. The vertical step is 64 units. Allocation proceeds down each column before moving to the next. The last child receives the requested heading and shared tail modifiers. Grids 0x14..0x17 also reach the native fallback that sets that child's health to 20; bronze grid 0x18 retains health 260 before difficulty modifiers. See the native replay evidence.

Algorithm sketch (high level)

1) Base init (always)

  • Allocates a creature slot (creature_alloc_slot()), then writes base fields:
  • ai_mode = 0, pos_xy, vel_xy = 0
  • active = 1, state_flag = 1
  • collision defaults (plague_infected = 0, dot_tick_timer = 0)
  • death_timer = 16, attack_cooldown = 0
  • Seeds a transient random heading early: crt_rand() % 0x13a * 0.01.
  • If heading == -100.0, randomizes the final heading: crt_rand() % 0x274 * 0.01.
  • creature_alloc_slot() itself consumes RNG to seed per-creature defaults (notably phase_seed).

Full pool: the phantom slot

creature_alloc_slot() has no failure path. When all 0x180 slots are active it logs "No free creatures to spawn!" (with cv_verbose) and returns 0x180, one past the pool, without clearing flags or drawing a phase seed. Callers write that creature anyway, into creature_pool[0x180]: the entry creature_pool_global_init still covers (it initializes 0x181 entries) but nothing iterates.

  • A formation spawned with k free slots puts its root and first k - 1 members in the pool; every later member overwrites the phantom slot, and the tail applies to it when it is the last member.
  • The phantom slot's fields persist between writes, flags included: a spawner that landed there keeps flag 0x4 for later spawns, whose tail then adjusts its spawn slot's interval again.
  • Links and spawn-slot owners can name the phantom slot (0x180). creature_update_all never visits it, so its spawn slot never ticks and is never freed until gameplay_reset_state, but player_apply_move_with_spawn_avoidance still blocks the player around its position.
  • A phantom slot that carries flag 0x4 with a formation link in link_index makes the tail index the spawn-slot table out of bounds; the port skips that write.

2) Template switch (template-specific)

Large switch/if-chain on template_id assigns template-specific constants and behavior:

  • Stats: type_id, flags, health, move_speed, reward_value, size, tint_rgba, ai_mode, and various AI/link fields.

  • Formation spawners: allocate N linked children and arrange them using circular offsets (cos/sin) and AI link modes (e.g. ai_mode = 3 with link_index = parent).

  • Spawn-slot spawners: allocate a slot (creature_spawn_slot_alloc()), store the slot index in link_index, and configure creature_spawn_slot_* arrays (timer/count/limit/interval/template/owner).

3) Tail modifiers (shared end-of-function)

Applied after the template switch to the returned creature:

  • If not in demo mode and inside terrain bounds: effect_spawn_burst(pos, 8).
  • max_health = health.
  • Note: difficulty/hardcore scaling applies to health after this assignment, so max_health retains the pre-scaled value.

  • Spider SP1 special case: when type_id == 3 and flags do not include 0x10 or 0x80, sets 0x80, clears link_index, and applies a move_speed *= 1.2 buff.

  • Template 0x38 special case: in hardcore, applies move_speed *= 0.7.

  • Overwrites heading with the final (possibly randomized) heading argument.
  • Difficulty / hardcore scaling:
  • Non-hardcore:

    • For flag 0x4 spawners: spawn_slot_interval += 0.2.
    • If quest_fail_retry_count > 0, scales reward/speed/contact/health, and for flag 0x4 spawners adds min(3.0, difficulty * 0.35) to spawn_slot_interval.
  • Hardcore:

    • Clears difficulty (quest_fail_retry_count = 0).
    • Buffs speed/contact/health.
    • For flag 0x4 spawners: spawn_slot_interval -= 0.2 clamped to >= 0.1.

Spawn template ids (direct type/flags map)

The large template switch inside creature_spawn_template assigns type_id and sometimes flags. The table below lists only these direct assignments (useful for labeling).

It does not capture randomized parameters, formation spawns, spawn slots, or tail modifiers. Spawn id 0x02 is SPAWN_ID_UNUSED_02 (spawn_id_t, tools/match/include/crimsonland_gameplay.h): it has no branch in decomp/1.9/crimsonland/quests/creature_spawn_template.cpp and is omitted below.

Generated by uv run scripts/gen_spawn_templates.py.

Spawn id (template_id) Type id Creature Flags (creature_flags) Anim note
0x0 0 zombie 0x44 long strip (0x40 overrides 0x4)
0x1 4 spider_sp2 0x8
0x3 3 spider_sp1 ``
0x4 1 lizard ``
0x5 4 spider_sp2 ``
0x6 2 alien ``
0x7 2 alien 0x4 short strip (ping-pong)
0x8 2 alien 0x4 short strip (ping-pong)
0x9 2 alien 0x4 short strip (ping-pong)
0xa 2 alien 0x4 short strip (ping-pong)
0xb 2 alien 0x4 short strip (ping-pong)
0xc 2 alien 0x4 short strip (ping-pong)
0xd 2 alien 0x4 short strip (ping-pong)
0xe 2 alien 0x4 short strip (ping-pong)
0xf 2 alien ``
0x10 2 alien 0x4 short strip (ping-pong)
0x11 1 lizard ``
0x12 2 alien ``
0x13 2 alien ``
0x14 2 alien ``
0x15 2 alien ``
0x16 1 lizard ``
0x17 3 spider_sp1 ``
0x18 2 alien ``
0x19 2 alien ``
0x1a 2 alien ``
0x1b 3 spider_sp1 ``
0x1c 1 lizard ``
0x1d 2 alien ``
0x1e 2 alien ``
0x1f 2 alien ``
0x20 2 alien ``
0x21 2 alien ``
0x22 2 alien ``
0x23 2 alien ``
0x24 2 alien ``
0x25 2 alien ``
0x26 2 alien ``
0x27 2 alien 0x400 bonus_id=WEAPON (3), amount_override=5 (packed in link_index)
0x28 2 alien ``
0x29 2 alien ``
0x2a 2 alien ``
0x2b 2 alien ``
0x2c 2 alien ``
0x2d 2 alien ``
0x2e 1 lizard ``
0x2f 1 lizard ``
0x30 1 lizard ``
0x31 1 lizard ``
0x32 3 spider_sp1 ``
0x33 3 spider_sp1 ``
0x34 3 spider_sp1 ``
0x35 4 spider_sp2 ``
0x36 2 alien ``
0x37 4 spider_sp2 0x100
0x38 3 spider_sp1 0x80
0x39 3 spider_sp1 0x80
0x3a 3 spider_sp1 0x10 projectile_type=9
0x3b 3 spider_sp1 ``
0x3c 3 spider_sp1 0x100 projectile_type=26 (packed in orbit_radius)
0x3d 3 spider_sp1 ``
0x3e 3 spider_sp1 ``
0x3f 3 spider_sp1 ``
0x40 3 spider_sp1 ``
0x41 0 zombie ``
0x42 0 zombie ``
0x43 0 zombie ``

Notes:

  • src/crimson/creatures/spawn.py contains both the spawn-id labeling index (direct type/flags) and the pure plan builder (formations/spawn slots/tail modifiers).

Spawn id sources (call sites)

template_id is supplied by a mix of scripted spawners and data tables:

  • demo_setup_variant_0 (0x00402ed0), demo_setup_variant_2 (0x00402fe0), demo_setup_variant_1 (0x004030f0), demo_setup_variant_3 (0x00403250): mode setup helpers called from demo_mode_start (0x00403390) (hard‑coded spawn ids like 0x34, 0x35, 0x38, 0x41, 0x24, 0x25).

  • survival_update (0x00407cd0): milestone spawns using 0x12, 0x2b, 0x2c, 0x35, 0x38, 0x3a, 0x3c, and 1. Regular enemy waves are spawned via survival_spawn_creature (0x00407510), which selects type/stats based on player_experience (not a spawn id). Python: survival_update in src/crimson/sim/mode_updates.py, survival_spawn_creature.

  • Rush mode (rush_mode_update, 0x004072b0): spawns edge waves via creature_spawn (type ids 2/3), not creature_spawn_template. Python: rush_mode_update in src/crimson/sim/mode_updates.py, creature_spawn.

  • Tutorial timeline (tutorial_timeline_update, 0x00408990): scripted spawns using 0x24, 0x26, 0x27, 0x28, 0x40. Python: tutorial_timeline_update in src/crimson/tutorial/timeline.py.

  • Quest/timeline spawner (quest_spawn_timeline_update, 0x00434250): pulls spawn ids from the table at quest_spawn_table (spawn id at +0x0c, count at +0x14). Python: quest_spawn_timeline_update in src/crimson/quests/timeline.py.

  • AI subspawns (creature_update_all): periodic spawns using creature_spawn_slot_table[i].template_id (entry size 0x18), which is seeded for some template ids inside creature_spawn_template (0x00430af0).

Quest spawn table (quest_spawn_table)

Quests populate a fixed table at quest_spawn_table (entry size 0x18, count in quest_spawn_count). quest_spawn_timeline_update (0x00434250) walks the table and spawns entries whose trigger time has elapsed.

Entry layout (dwords):

Offset Field Notes
0x00 x base spawn X; if offscreen, the group spreads along Y instead of X.
0x04 y base spawn Y.
0x08 heading passed as heading to creature_spawn_template.
0x0c spawn id cast to int and passed as template_id to creature_spawn_template.
0x10 trigger time compared against quest_spawn_timeline (quest clock).
0x14 count number of spawns in the group; decremented to 0 after firing.

Notes:

  • quest_start_selected (0x0043a790) chooses a quest builder from the table at quest_meta_table. The function pointer lives at &quest_selected_builder; when null, it falls back to quest_build_fallback (0x004343e0) (two entries with spawn id 0x40, counts 10/0x14, trigger times 500/5000).

  • quest_build_zombie_time (0x00437d70), quest_build_lizard_raze (0x00438840), and quest_build_surrounded_by_reptiles (0x00438940) are examples of quest builders that write multiple quest_spawn_table entries with varying spawn ids and timings.

Repo references

  • Recovered native source: decomp/1.9/crimsonland/quests/creature_spawn_template.cpp
  • Native-oracle tests: tests/native_oracle/test_spawn_template.py, tests/native_oracle/test_spawn_full_pool.py
  • Creature pool + spawn-slot fields: docs/creatures/struct.md
  • Rewrite model (pure plan builder): src/crimson/creatures/spawn.py
  • Survival mode: survival_update in src/crimson/sim/mode_updates.py, survival_spawn_creature in src/crimson/creatures/spawn.py
  • Tests: tests/modes/test_survival_milestones.py, tests/modes/test_survival_wave.py, tests/modes/test_survival_spawn.py
  • Rush mode: rush_mode_update in src/crimson/sim/mode_updates.py, creature_spawn in src/crimson/creatures/spawn.py
  • Tests: tests/modes/test_rush_mode_spawn.py
  • Tutorial timeline: src/crimson/tutorial/timeline.py
  • tutorial_timeline_update (spawns into the world after the world render, before the level-up check)
  • Tests: tests/modes/test_tutorial_timeline_update.py, tests/native_oracle/test_tutorial_timeline.py
  • Quest timeline: src/crimson/quests/timeline.py
  • quest_spawn_timeline_update (spawns into the world), quest_spawn_table_empty
  • Tests: tests/modes/test_quest_spawn_timeline.py
  • MSVCRT-compatible RNG for deterministic replays: src/grim/rand.py (Crand)