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:
- Probe Detection System - Guards detect Link entering restricted areas
- Guard Capture Mechanics - Guards surround and capture Link
- Warp System - Link is transported to a dungeon entrance
- 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:
Oracle_CaptureAndWarp(DUPLICATE - already in Core/capture.asm)Hooked_Guard_Main- Modified guard behavior
Issues:
- Remove duplicate
Oracle_CaptureAndWarpfunction - Complete
Hooked_Guard_Mainimplementation - 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 implementationDocs/Features/CastleAmbush.md- System documentationDocs/Sprites/Enemies/CastleGuard.md- Sprite documentation
Modify:
Core/capture.asm- Add enhanced versionCore/sram.asm- Add SRAM flagsCore/symbols.asm- Add constantsSprites/all_sprites.asm- Include castle_guard.asm
Delete/Consolidate:
Sprites/Enemies/custom_guard.asm- Consolidate into castle_guard.asm- Remove duplicate
Oracle_CaptureAndWarpfrom custom_guard.asm
Questions to Resolve
- Entrance ID: Which dungeon entrance should be used for the cell?
- Quest Integration: When should castle ambush activate?
- After certain quest milestone?
- When Link enters specific castle area?
- Triggered by dialogue/cutscene?
- Difficulty Scaling: Should capture difficulty increase after first capture?
- Escape Sequence: How should the escape play out?
- Find key item?
- Stealth section?
- Fight way out?
- Sprite Slot: New sprite ID or override vanilla guard ($41/$42/$43)?
See Also
Docs/Sprites/ProbeSprites.md- Probe detection systemDocs/Sprites/Enemies/Darknut.md- Similar guard-type enemyDocs/Guides/SpriteCreationGuide.md- Sprite creation referenceCore/capture.asm- Core warp functionalitySprites/experimental/probe.asm- Probe system reference