Files
oracle-of-secrets/Docs/Features/CastleAmbush_Plan.md

18 KiB

Castle Ambush & Guard Capture System - Implementation Plan

Status: 🚧 Planning Phase
Created: October 3, 2025
Target: Future Update
Related Files: Core/capture.asm, Sprites/Enemies/custom_guard.asm, Sprites/overlord_ref.asm


Overview

The Castle Ambush System will create a dynamic encounter where Link is detected by guards in the castle, captured, and warped to a dungeon. This combines:

  1. Probe Detection System - Guards detect Link entering restricted areas
  2. Guard Capture Mechanics - Guards surround and capture Link
  3. Warp System - Link is transported to a dungeon entrance
  4. Overlord Management - Multi-screen guard coordination

Current State Analysis

Existing Components

Core/capture.asm

Status: Implemented but untested

Oracle_CaptureAndWarp:
{
  STA.w $010E        ; Set the target entrance ID
  LDA.b #$05         ; Game Mode 05: (hole/whirlpool transition)
  STA.b $10          ; Set the game mode
  STZ.b $2F          ; Clear Link's action state
  STZ.b $5D          ; Clear Link's state
  LDA.b #$02 : STA.b $71  ; Set transition flag
  RTL
}

Purpose: Warps Link to a specific dungeon entrance (like WallMaster)

Issues to Address:

  • Test entrance ID values (need to determine correct dungeon entrance)
  • Verify game mode $05 works for this use case
  • Add screen fade/transition effect
  • Play capture sound effect
  • Store pre-capture location for potential escape sequence

🚧 Sprites/Enemies/custom_guard.asm

Status: Prototype with duplicate code

Contains:

  1. Oracle_CaptureAndWarp (DUPLICATE - already in Core/capture.asm)
  2. Hooked_Guard_Main - Modified guard behavior

Issues:

  • Remove duplicate Oracle_CaptureAndWarp function
  • Complete Hooked_Guard_Main implementation
  • Test guard capture trigger conditions
  • Integrate with vanilla guard sprites (ID $41, $42, $43)

📚 Sprites/overlord_ref.asm

Status: Reference material (now in experimental/)

Purpose: Documents overlord path patterns for crumbling tiles

Relevance: Can be adapted for guard patrol paths


System Architecture

Phase 1: Detection (Probe System)

┌─────────────────────────────────────────────────┐
│ Link enters castle restricted area              │
│ SRAM flag: $7EF??? = Castle infiltration active │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│ Guard spawns probe sprites every 32 frames      │
│ Probe checks:                                   │
│  - Link within 16px radius                      │
│  - Same floor layer ($0F20)                     │
│  - Not invisible/bunny                          │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
          ┌────────┴────────┐
          │  Probe Hit?     │
          └────────┬────────┘
                   │
      ┌────────────┼────────────┐
      │ YES                     │ NO
      ▼                         ▼
┌──────────────┐        ┌──────────────┐
│ Trigger      │        │ Continue     │
│ Alert State  │        │ Patrol       │
└──────┬───────┘        └──────────────┘
       │
       ▼
┌──────────────────────────────────────┐
│ Play alert sound ($1D)               │
│ Set SprTimerD = $B0 (176 frames)    │
│ Spawn reinforcement guards           │
└──────────────────────────────────────┘

Phase 2: Pursuit & Capture

┌─────────────────────────────────────────────────┐
│ Alert state active (SprTimerD > 0)              │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│ Guards converge on Link's position              │
│  - Use Guard_ChaseLinkOnOneAxis                 │
│  - Spawn additional guards from off-screen      │
│  - Maximum 4 guards active                      │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│ Check capture conditions (every frame)          │
│  - Link is surrounded (guards on 3+ sides)      │
│  - Link is not moving (speed = 0)               │
│  - Link has taken damage from guard             │
└──────────────────┬──────────────────────────────┘
                   │
          ┌────────┴────────┐
          │  Captured?      │
          └────────┬────────┘
                   │
      ┌────────────┼────────────┐
      │ YES                     │ NO
      ▼                         ▼
┌──────────────┐        ┌──────────────┐
│ Initiate     │        │ Continue     │
│ Capture      │        │ Chase        │
└──────┬───────┘        └──────────────┘
       │
       ▼
┌──────────────────────────────────────┐
│ Phase 3: Warp Sequence               │
└──────────────────────────────────────┘

Phase 3: Warp Sequence

┌─────────────────────────────────────────────────┐
│ Freeze Link (disable input)                     │
│ Play capture animation                          │
│  - Link's sprite changes to "captured" pose     │
│  - Guards move to surround positions            │
│  - Screen shake effect (3 frames)               │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│ Fade out screen ($0012 = $01)                   │
│ Wait 32 frames                                  │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│ Call Oracle_CaptureAndWarp                      │
│  - A register = Dungeon entrance ID             │
│  - Sets game mode to $05 (transition)           │
│  - Clears Link state                            │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────┐
│ Link spawns in dungeon cell                     │
│  - Set SRAM flag: $7EF??? = Captured            │
│  - Play jingle ($06)                            │
│  - Trigger escape sequence                      │
└──────────────────────────────────────────────────┘

File Consolidation Plan

Step 1: Organize Core Utilities

Keep in Core/capture.asm:

; Core warp functionality
Oracle_CaptureAndWarp:
{
  ; (existing implementation)
}

; NEW: Enhanced version with effects
Oracle_CaptureAndWarp_Enhanced:
{
  PHP
  
  ; Store entrance ID
  STA.w $010E
  
  ; Store capture flag in SRAM
  LDA.b #$01
  STA.l $7EF3D8  ; Custom flag: Has been captured
  
  ; Play capture sound
  LDA.b #$1D  ; Alert/Capture sound
  STA.w $012E
  
  ; Fade out screen
  LDA.b #$01
  STA.w $0012  ; Request fade
  
  ; Set up timer for transition
  LDA.b #$40  ; Wait 64 frames
  STA.b $00
  
  .wait_fade
    LDA.b $00 : BNE .wait_fade
  
  ; Execute warp
  LDA.w $010E  ; Get entrance ID back
  STA.w $010E
  
  LDA.b #$05   ; Game Mode 05
  STA.b $10
  STZ.b $2F
  STZ.b $5D
  
  LDA.b #$02
  STA.b $71
  
  PLP
  RTL
}

Step 2: Create Unified Guard Sprite

New file: Sprites/Enemies/castle_guard.asm

This will replace Sprites/Enemies/custom_guard.asm with a complete implementation:

; =========================================================
; Castle Guard - Ambush & Capture Variant
; =========================================================

!SPRID = Sprite_CastleGuard  ; Use new sprite ID or override vanilla
!NbrTiles = 02
!Health = 08
!Damage = 04
; ... other properties ...

%Set_Sprite_Properties(Sprite_CastleGuard_Prep, Sprite_CastleGuard_Long)

; States
!STATE_PATROL = 0
!STATE_ALERT = 1
!STATE_CHASE = 2
!STATE_CAPTURE = 3

Sprite_CastleGuard_Long:
{
  PHB : PHK : PLB
  JSR Sprite_CastleGuard_Draw
  JSL Sprite_DrawShadow
  JSL Sprite_CheckActive : BCC .inactive
    JSR Sprite_CastleGuard_Main
  .inactive
  PLB
  RTL
}

Sprite_CastleGuard_Prep:
{
  PHB : PHK : PLB
  
  ; Check if castle ambush is active
  LDA.l $7EF3D7 : BEQ .no_ambush  ; Custom flag: Castle ambush active
    ; Enable enhanced AI
    LDA.b #$01 : STA.w $0E80, X  ; Custom flag for this guard
  .no_ambush
  
  ; Standard health based on sword level
  LDA.l $7EF359 : TAY
  LDA.w .health, Y : STA.w SprHealth, X
  
  ; Enable parrying
  LDA.b #$80 : STA.w SprDefl, X
  
  PLB
  RTL
  
  .health
    db $04, $06, $08, $0A
}

Sprite_CastleGuard_Main:
{
  ; State machine
  LDA.w SprAction, X
  JSL JumpTableLocal
  
  dw CastleGuard_Patrol
  dw CastleGuard_Alert
  dw CastleGuard_Chase
  dw CastleGuard_Capture
}

CastleGuard_Patrol:
{
  ; Check if castle ambush should activate
  LDA.l $7EF3D7 : BEQ .normal_patrol
  
  ; Check distance to Link
  JSL GetDistance8bit_Long : CMP.b #$80 : BCS .no_probe
  
  ; Spawn probe for detection
  LDA.w SprTimerA, X : BNE .no_probe
    LDA.b #$20 : STA.w SprTimerA, X  ; Spawn every 32 frames
    JSL Sprite_SpawnProbeAlways_long
  .no_probe
  
  ; Check if probe triggered alert
  LDA.w SprTimerD, X : BEQ .normal_patrol
    ; Probe detected Link!
    LDA.b #!STATE_ALERT : STA.w SprAction, X
    
    ; Play alert sound
    LDA.b #$1D : STA.w $012E
    
    ; Spawn reinforcements
    JSR CastleGuard_SpawnReinforcements
    
    RTS
  
  .normal_patrol
  ; Standard patrol behavior
  JSR Guard_StandardPatrol
  RTS
}

CastleGuard_Alert:
{
  ; Transition to chase after alert plays
  LDA.w SprTimerD, X : CMP.b #$A0 : BCS .stay_alert
    LDA.b #!STATE_CHASE : STA.w SprAction, X
  .stay_alert
  
  ; Face Link
  JSL Sprite_DirectionToFacePlayer : TYA : STA.w SprMiscC, X
  
  ; Draw with alert animation
  LDA.b #$08 : STA.w SprGfx, X
  RTS
}

CastleGuard_Chase:
{
  ; Move toward Link
  LDA.b #$0C : JSL Sprite_ApplySpeedTowardsPlayer
  JSL Guard_ChaseLinkOnOneAxis
  JSL Sprite_Move
  JSL Sprite_BounceFromTileCollision
  
  ; Check if Link is surrounded
  JSR CastleGuard_CheckSurrounded : BCC .not_surrounded
    ; Capture Link!
    LDA.b #!STATE_CAPTURE : STA.w SprAction, X
    LDA.b #$60 : STA.w SprTimerC, X  ; Capture animation duration
    
    ; Freeze Link
    LDA.b #$17 : STA.b $5D  ; Link state: captured
    STZ.b $67  ; Stop Link's movement
    RTS
  
  .not_surrounded
  ; Continue chase
  JSL Guard_ParrySwordAttacks
  JSL Sprite_CheckDamageFromPlayer
  RTS
}

CastleGuard_Capture:
{
  ; Animate capture sequence
  LDA.w SprTimerC, X : BNE .animating
    ; Capture complete - warp Link
    LDA.b #$42  ; Entrance ID for dungeon cell
    JSL Oracle_CaptureAndWarp_Enhanced
    RTS
  
  .animating
  ; Guards surround Link
  ; ... capture animation logic ...
  RTS
}

CastleGuard_CheckSurrounded:
{
  ; Count guards within range in each direction
  ; Return carry set if Link is surrounded
  ; (3+ guards within 32px, covering different quadrants)
  
  ; ... implementation ...
  
  CLC  ; Not surrounded
  RTS
}

CastleGuard_SpawnReinforcements:
{
  ; Spawn additional castle guards off-screen
  LDY.b #$00
  LDX.b #$0F
  
  .spawn_loop
  LDA.w $0DD0, X : BEQ .found_slot
    DEX : BPL .spawn_loop
    RTS  ; No free slots
  
  .found_slot
  ; Spawn guard sprite
  LDA.b #Sprite_CastleGuard : STA.w $0E20, X
  LDA.b #$09 : STA.w $0DD0, X  ; Active state
  
  ; Position off-screen based on Link's position
  ; ... positioning logic ...
  
  ; Set to chase state immediately
  LDA.b #!STATE_CHASE : STA.w SprAction, X
  
  INY
  CPY.b #$03 : BCC .spawn_loop  ; Spawn up to 3 reinforcements
  
  RTS
}

; ... drawing routine ...

Step 3: Integrate with Existing Systems

Modify Sprites/all_sprites.asm:

org $318000
%log_start("castle_guard", !LOG_SPRITES)
incsrc "Sprites/Enemies/castle_guard.asm"
%log_end("castle_guard", !LOG_SPRITES)

Add SRAM Flags to Core/sram.asm:

; Castle Ambush System
$7EF3D7 = CastleAmbushActive    ; 01 = ambush scenario active
$7EF3D8 = HasBeenCaptured       ; 01 = player has been captured before
$7EF3D9 = CaptureCount          ; Number of times captured

Add Constants to Core/symbols.asm:

; Castle Ambush
CastleAmbushActive = $7EF3D7
HasBeenCaptured = $7EF3D8
CaptureCount = $7EF3D9

Testing Plan

Test Case 1: Detection

  • Enter castle area with ambush flag set
  • Walk near guard
  • Verify probe spawns every 32 frames
  • Walk into probe's path
  • Verify alert sound plays
  • Verify guard enters alert state

Test Case 2: Chase

  • Continue from Test Case 1
  • Verify guard chases Link
  • Verify reinforcements spawn
  • Verify multiple guards coordinate
  • Verify guards use parrying

Test Case 3: Capture

  • Let guards surround Link
  • Verify capture check works
  • Verify Link is frozen
  • Verify capture animation plays
  • Verify screen fades out

Test Case 4: Warp

  • Continue from Test Case 3
  • Verify Link warps to dungeon
  • Verify SRAM flag is set
  • Verify Link spawns in correct room
  • Verify capture count increments

Test Case 5: Escape

  • Escape from dungeon cell
  • Return to castle
  • Verify guards remember previous capture
  • Verify harder difficulty on subsequent captures

Implementation Phases

Phase A: Core Functionality (Week 1)

  • Clean up Core/capture.asm
  • Add Oracle_CaptureAndWarp_Enhanced
  • Test basic warp functionality
  • Determine correct entrance ID for dungeon cell

Phase B: Guard AI (Week 2)

  • Create Sprites/Enemies/castle_guard.asm
  • Implement probe detection
  • Implement state machine
  • Test patrol → alert → chase transitions

Phase C: Capture Mechanics (Week 3)

  • Implement surround check
  • Implement capture animation
  • Test capture trigger conditions
  • Add sound effects

Phase D: Integration (Week 4)

  • Add SRAM flags
  • Integrate with quest system
  • Create dungeon escape sequence
  • Test full cycle

Phase E: Polish (Week 5)

  • Add dialogue/cutscenes
  • Add visual effects
  • Balance difficulty
  • Add achievements/tracking

Entrance IDs Reference

Need to determine correct entrance for dungeon cell:

; Common dungeon entrances
$00 = Hyrule Castle (main entrance)
$04 = Hyrule Castle (throne room)
$0E = Hyrule Castle (dark passage)
$20 = Eastern Palace
$42 = Dark Palace
$?? = Custom dungeon cell (TBD)

Action Required: Find or create appropriate dungeon cell entrance


Files to Create/Modify

Create:

  • Sprites/Enemies/castle_guard.asm - Main guard implementation
  • Docs/Features/CastleAmbush.md - System documentation
  • Docs/Sprites/Enemies/CastleGuard.md - Sprite documentation

Modify:

  • Core/capture.asm - Add enhanced version
  • Core/sram.asm - Add SRAM flags
  • Core/symbols.asm - Add constants
  • Sprites/all_sprites.asm - Include castle_guard.asm

Delete/Consolidate:

  • Sprites/Enemies/custom_guard.asm - Consolidate into castle_guard.asm
  • Remove duplicate Oracle_CaptureAndWarp from custom_guard.asm

Questions to Resolve

  1. Entrance ID: Which dungeon entrance should be used for the cell?
  2. Quest Integration: When should castle ambush activate?
    • After certain quest milestone?
    • When Link enters specific castle area?
    • Triggered by dialogue/cutscene?
  3. Difficulty Scaling: Should capture difficulty increase after first capture?
  4. Escape Sequence: How should the escape play out?
    • Find key item?
    • Stealth section?
    • Fight way out?
  5. Sprite Slot: New sprite ID or override vanilla guard ($41/$42/$43)?

See Also

  • Docs/Sprites/ProbeSprites.md - Probe detection system
  • Docs/Sprites/Enemies/Darknut.md - Similar guard-type enemy
  • Docs/Guides/SpriteCreationGuide.md - Sprite creation reference
  • Core/capture.asm - Core warp functionality
  • Sprites/experimental/probe.asm - Probe system reference