attack

package
v0.5.0-beta Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const DeployBudget = 170 * time.Second

DeployBudget bounds the entire deploy phase (strategy phases + spell / hero reconcile + sweep + verify) from the moment DeployDynamicV2 starts. A CoC battle lasts 3 minutes from the first drop; every reconcile/sweep loop below consults DeployBudgetExhausted so a slot whose "empty" check keeps failing (observed live: a spent earthquake/spell card stuck in its cooldown read as visually non-empty forever) can never keep firing taps past the battle timer — the stuck-sweep symptom that burned minutes and hundreds of taps after the battle was already over. The budget is ~2x the longest legitimately-observed full deployment (~100s) so real attacks are never cut short.

Variables

This section is empty.

Functions

func GetAllCounts

func GetAllCounts(counts []TroopCount) map[int]int

GetAllCounts returns a map of slot X -> count.

func GetCountForSlot

func GetCountForSlot(counts []TroopCount, slotX int) int

GetCountForSlot returns the detected count for a specific slot X coordinate.

func GetSlotActivityRatioStatic

func GetSlotActivityRatioStatic(screen gocv.Mat, x, y, screenW int) float64

GetSlotActivityRatioStatic returns the ratio of active content pixels in a slot region.

func GetStrategyUnitNames

func GetStrategyUnitNames(s *strategy.DynamicStrategy) []string

GetStrategyUnitNames returns all unit names from a strategy.

func NextEdgeIndex

func NextEdgeIndex() string

NextEdgeIndex atomically advances the persistent rotation counter and returns the next corner name in the cycle.

Failure modes (all degraded gracefully, never panic, never block):

  • File missing: first call returns index 0 (TopLeft) and persists the new state.
  • File empty: same as missing.
  • File corrupted (invalid JSON): same as missing; the next call overwrites with valid JSON.
  • File with out-of-range LastIndex (e.g. -1, 99): defensively reset to 0, then advance normally — no crash on weird persistent state.
  • File write error (disk full, permission denied): log to stderr, still return the computed next index. Cross-restart continuity is lost in this case but the bot keeps cycling.

The call is wrapped in rotationMu.Lock/Unlock so concurrent invocations from the bot's parallel attack goroutines don't drop increments or interleave read/write.

Types

type DeployLine

type DeployLine struct {
	Points  []image.Point
	Side    string
	Anchor  image.Point
	Outside bool
}

DeployLine represents a calculated deployment line.

type DeployLineCalculator

type DeployLineCalculator struct {
	// contains filtered or unexported fields
}

DeployLineCalculator computes deployment lines dynamically.

func NewDeployLineCalculator

func NewDeployLineCalculator(logger zerolog.Logger) *DeployLineCalculator

NewDeployLineCalculator creates calculator.

func (*DeployLineCalculator) Calculate

func (d *DeployLineCalculator) Calculate(
	zone RedZone,
	screenW, screenH, uiCutoff int,
	preferSide string,
	count int,
) DeployLine

Calculate returns a deployment line outside the red zone. Picks edge with most free space, places line 80px outside red zone.

type DeployPlanner

type DeployPlanner struct {
	// contains filtered or unexported fields
}

DeployPlanner resolves strategy YAML into concrete deployment plans.

func NewDeployPlanner

func NewDeployPlanner(
	slotManager *SlotManager,
	pCfg PrecisionConfig,
	targetEdge string,
	w, h int,
	logger zerolog.Logger,
) *DeployPlanner

NewDeployPlanner creates a new deployment planner.

func (*DeployPlanner) PlanDeployment

func (dp *DeployPlanner) PlanDeployment(s *strategy.DynamicStrategy) []PhasePlan

PlanDeployment resolves all phases into concrete deployment plans.

type Executor

type Executor struct {
	OnPhaseStart func(phase string, edge string)
	OnUnitDeploy func(unit string, slotX int, slotY int)

	OnDukePick func(targetEdge string, chosenEdge string)
	// contains filtered or unexported fields
}

func NewExecutor

func NewExecutor(client *adb.Client, cal *game.Calibration, cfg *config.AttackConfig, logger zerolog.Logger) *Executor

func (*Executor) CalculateInBetween

func (e *Executor) CalculateInBetween(edge string, offset int, bT, bB, bL, bR, fT, fB, fL, fR image.Point) (p1, p2 image.Point)

func (*Executor) DeployDynamic

func (e *Executor) DeployDynamic(s *strategy.DynamicStrategy, screen gocv.Mat) (int, error)

func (*Executor) DeployDynamicV2

func (e *Executor) DeployDynamicV2(s *strategy.DynamicStrategy, screen gocv.Mat, strategyPath string) (int, error)

DeployDynamicV2 deploys troops using dynamic red line detection. No hardcoded precision_config.json needed - detects deployment boundary live.

strategyPath is the on-disk YAML path. The orchestrator uses it to find the matching formula.json (loaded as <stem>_formula.json next to the YAML). Pass "" to skip formula lookup entirely.

func (*Executor) EndBattle

func (e *Executor) EndBattle() error

func (*Executor) GetSlotActivityRatio

func (e *Executor) GetSlotActivityRatio(screen gocv.Mat, x, y int) float64

GetSlotActivityRatio is the exported wrapper for debug scripts.

func (*Executor) GetSlotY

func (e *Executor) GetSlotY(h, mBarY int) int

GetSlotY returns the Y coordinate used for slot detection.

func (*Executor) GetTemplates

func (e *Executor) GetTemplates() map[string]gocv.Mat

func (*Executor) IsSlotEmpty

func (e *Executor) IsSlotEmpty(screen gocv.Mat, x, y int) bool

func (*Executor) LastDestructionPercent

func (e *Executor) LastDestructionPercent() int

LastDestructionPercent returns the highest destruction percentage the battle-end wait measured from the stall ROI (0 when nothing was read).

func (*Executor) MaximizeLineSpread

func (e *Executor) MaximizeLineSpread(p1, p2 image.Point, w, mBarY int) (image.Point, image.Point)

func (*Executor) ParseLayout

func (e *Executor) ParseLayout(screen gocv.Mat, pCfg PrecisionConfig, w, h, mBarY int) []TroopSlot

func (*Executor) ResetBattleOutcome

func (e *Executor) ResetBattleOutcome()

ResetBattleOutcome clears the per-battle destruction/TH state. Called at the top of WaitForBattleEndCtx so a misread from a previous battle can never bleed into the next battle's star computation (observed live: battle 1's garbage 381% was still latched on the shared Executor when battle 2's result was parsed, turning a 32% defeat into "3 stars").

func (*Executor) ReturnHome

func (e *Executor) ReturnHome() error

func (*Executor) SetActiveStrategy

func (e *Executor) SetActiveStrategy(s *strategy.DynamicStrategy)

SetActiveStrategy records the strategy being executed so battle-end waiting can honor per-strategy knobs (end_at_percent). Pass nil to clear.

func (*Executor) SetClassifier

func (e *Executor) SetClassifier(fn func(gocv.Mat) (game.GameState, int))

func (*Executor) SweepRemainingSlots

func (e *Executor) SweepRemainingSlots(screen gocv.Mat, pCfg PrecisionConfig, targetEdge string, w, h int, mBarY int, usedSlots map[int]bool, siegeXs []int, allSlots []TroopSlot, slotY int)

func (*Executor) ThDestroyed

func (e *Executor) ThDestroyed() bool

ThDestroyed reports whether the golden Town Hall destroyed banner was seen during the wait (false when no th_banner_zone is configured, i.e. the TH state is unknown).

func (*Executor) UpdateConfig

func (e *Executor) UpdateConfig(cfg *config.AttackConfig)

func (*Executor) Validate

func (e *Executor) Validate(s *strategy.DynamicStrategy) error

Validate ensures all required templates for the strategy exist or are covered by manual labels

func (*Executor) WaitForBattleEnd

func (e *Executor) WaitForBattleEnd(timeout time.Duration) bool

WaitForBattleEnd is the context-free variant of WaitForBattleEndCtx. Kept as the stable entry point for callers (CLI, tests) that don't need cancellation.

func (*Executor) WaitForBattleEndCtx

func (e *Executor) WaitForBattleEndCtx(ctx context.Context, timeout time.Duration) bool

type HeroDeployment

type HeroDeployment struct {
	Unit      strategy.Unit
	Slot      *TrackedSlot
	IsAbility bool
}

HeroDeployment represents a resolved hero deployment.

type HeroManager

type HeroManager struct {
	OnDukeDeployed func(targetEdge string)
	// contains filtered or unexported fields
}

HeroManager handles hero-specific deployment logic.

func NewHeroManager

func NewHeroManager(
	executor *TapExecutor,
	slotManager *SlotManager,
	pCfg PrecisionConfig,
	targetEdge string,
	w, h int,
	formula *formula.Formula,
	troopCounter *TroopCounter,
	logger zerolog.Logger,
) *HeroManager

NewHeroManager creates a new hero manager. formula may be nil; when non-nil, per-unit formula entries override the legacy pCfg.Edges / dynamic red-zone deploy coordinates so the user can pin exact side positions via cmd/design_attack. troopCounter may also be nil; when non-nil, DeployTroops uses it to live-OCR the slot's per-card count at deploy time AND after the main tap pass — so balloons/EDs (and any "amount: All" troop) always reach a true empty state before being marked deployed.

func (*HeroManager) DeployHeroes

func (hm *HeroManager) DeployHeroes(heroUnits []strategy.Unit, screen gocv.Mat) []*TrackedSlot

DeployHeroes deploys all heroes and activates abilities. Returns list of deployed hero slots for ability tracking.

func (*HeroManager) DeploySiege

func (hm *HeroManager) DeploySiege(unit strategy.Unit, slot *TrackedSlot) bool

DeploySiege deploys a siege machine.

Formula takes precedence: when the user authored a "point" or "line" entry for this siege (e.g. stone_slammer → {"type":"point","p":{...}}), the user-pinned geometry wins and the legacy pCfg.Edges path is skipped. Falls back to the dynamic red-zone edge line otherwise.

Live-test fix: ON SUCCESS the slot is marked deployed via slot.UnitName (canonical key in unitIndex), NOT via unit.Name. Template matching may identify the slot under a slightly different spelling than the strategy YAML uses, so the strategy-derived `unit.Name` key often fails GetSlot() lookup and MarkDeployed becomes a silent no-op. That left the slot in a non-terminal state and the sweeper picked it up — 3 deploySlot retries × ~12 taps each = ~36 wasted taps per attack.

Live-test fix #2: the legacy path no longer polls isSlotEmptyStatic after the drop. Siege machines in CoC NEVER transition the troop-bar slot back to a clean "empty" state on success — the slot visually persists as the next queued icon (often a CC-troop icon) or a skeleton silhouette until the next production cycle. Trusting the tap and marking deployed directly is the only safe path.

func (*HeroManager) DeployTroops

func (hm *HeroManager) DeployTroops(
	unit strategy.Unit,
	slot *TrackedSlot,
	pattern string,
	offset int,
	phasePattern string,
	screen gocv.Mat,
	detectedCount int,
) bool

DeployTroops deploys a group of regular troops (non-hero, non-spell).

Root-cause fix for the "balloons/EDs sometimes don't all get placed" user-reported bug: the previous path fired `count` taps and either returned success (formula-driven) or did a single visual-empty check, then marked the slot SlotDeployed regardless of whether troop icons remained. When the cached detectedCount was wrong (template-OCR is brittle across themes / emulator sizes), troops were left behind without ever being re-counted.

The new path:

  1. Live-OCR the slot's per-card count BEFORE the main tap pass. Live count wins over detectedCount/YAML when > 0. When OCR fails AND the slot is visually empty, the deploy is a true no-op and we mark deployed without firing taps.
  2. Main pass fires exactly `count` taps on the formula/pinned or legacy edge line.
  3. Reconcile loop: up to reconcileRounds rounds, each one captures a fresh screen, live-OCRs + visual-empty checks, and re-selects + fires compensating taps if there's still count > 0. The slot is only MarkDeployed after a (live OCR == 0 AND visual-empty) confirmation — or after the reconcile budget runs out, in which case we record SlotAttempted so the sweep phase retries with its own reconcile loop.

type ManualEdge

type ManualEdge struct {
	P1 image.Point `json:"p1"`
	P2 image.Point `json:"p2"`
}

func ScaleEdge

func ScaleEdge(e ManualEdge, refW, refH, curW, curH int) ManualEdge

type PhasePlan

type PhasePlan struct {
	Phase     strategy.Phase
	UnitPlans []UnitPlan
	Edge      string
}

PhasePlan represents a resolved deployment plan for a phase.

type PrecisionConfig

type PrecisionConfig struct {
	Edges map[string]ManualEdge `json:"edges"`

	Sides        map[string]ManualEdge  `json:"sides,omitempty"`
	SpellEdgesA  map[string]ManualEdge  `json:"spell_edges_a"`
	SpellEdgesB  map[string]ManualEdge  `json:"spell_edges_b"`
	HeroTargets  map[string]image.Point `json:"hero_targets"`
	SpellTargets map[string]image.Point `json:"spell_targets"`
	BarY         int                    `json:"bar_y"`
	Width        int                    `json:"width"`
	Height       int                    `json:"height"`
}

type RedLineDetector

type RedLineDetector struct {
	// contains filtered or unexported fields
}

RedLineDetector finds the red deployment boundary on screen.

func NewRedLineDetector

func NewRedLineDetector(logger zerolog.Logger) *RedLineDetector

NewRedLineDetector creates detector.

func (*RedLineDetector) Detect

func (r *RedLineDetector) Detect(screen gocv.Mat, uiCutoff int) RedZone

Detect finds the red deployment boundary in the screenshot. Returns bounding box of the red zone (the no-deploy area). Troops must deploy OUTSIDE this box.

type RedZone

type RedZone struct {
	BBox     image.Rectangle
	Valid    bool
	Contours int
}

RedZone represents detected deployment boundary.

type RetryPolicy

type RetryPolicy struct {
	MaxRetries  int
	RetryDelay  time.Duration
	VerifyAfter bool
}

RetryPolicy defines retry behavior for a unit deployment.

type RotationState

type RotationState struct {
	LastIndex int `json:"last_index"`
}

RotationState is the on-disk schema for the persistent rotation index. Survives process restarts so the bot distributes attacks evenly across the 4 sides over time (not "always start at TopLeft on launch").

type SlotManager

type SlotManager struct {
	// contains filtered or unexported fields
}

SlotManager handles slot detection, classification, identity resolution, and state tracking.

func NewSlotManager

func NewSlotManager(
	screen gocv.Mat,
	pCfg PrecisionConfig,
	w, h, mBarY int,
	templates map[string]gocv.Mat,
	classify func(gocv.Mat) (game.GameState, int),
	logger zerolog.Logger,
) *SlotManager

NewSlotManager detects active slots, resolves identities via template matching + manual labels.

func (*SlotManager) GetAllSlots

func (sm *SlotManager) GetAllSlots() []*TrackedSlot

GetAllSlots returns all tracked slots.

func (*SlotManager) GetBarY

func (sm *SlotManager) GetBarY() int

GetBarY returns the Y coordinate of the troop-bar top (where deck counts are printed above each card). HeroManager / Sweeper / Verifier use this to live-OCR the per-card count above each slot.

func (*SlotManager) GetEventTroops

func (sm *SlotManager) GetEventTroops(strategyUnitNames []string) []*TrackedSlot

GetEventTroops returns slots with unit names not in the given strategy unit list.

"Event" here means any Troop/Spell/CC slot that the strategy did NOT declare. Bonus/seasonal event troops change names every season and may template-match to nothing (empty UnitName), so we deliberately:

  • include empty-UnitName slots (an unlabelled troop card is still a card the user wants placed),
  • include every Troop/Spell/CC slot whose name is not a strategy unit (covers seasonal names that never appear in the YAML),
  • include fallback-labeled slots REGARDLESS of category/name — the label comes from manual_labels.json (an old army) and can be a hero name ("archer queen") that collides with a strategy unit. If template matching couldn't ID the card, it's probably a bonus troop, so don't let a stale hero label hide it.

Slots already Deployed/Failed are skipped so the sweep doesn't re-fire slots the strategy phase already drained. Siege is included because a non-strategy siege slot (e.g. a seasonal bonus troop that got fallback-labeled "siege machine") is still a card the user wants placed; the strategy's OWN siege is excluded by its declared name.

func (*SlotManager) GetSlot

func (sm *SlotManager) GetSlot(unitName string) *TrackedSlot

GetSlot returns the tracked slot for a unit name (case-insensitive).

func (*SlotManager) GetSlotY

func (sm *SlotManager) GetSlotY() int

GetSlotY returns the Y coordinate used for slot detection.

func (*SlotManager) GetUndeployedSlots

func (sm *SlotManager) GetUndeployedSlots() []*TrackedSlot

GetUndeployedSlots returns slots not in Deployed or Failed state.

func (*SlotManager) MarkDeployed

func (sm *SlotManager) MarkDeployed(unitName string)

MarkDeployed marks a slot as successfully deployed.

func (*SlotManager) MarkFailed

func (sm *SlotManager) MarkFailed(unitName string)

MarkFailed marks a slot as failed after exhausting retries.

func (*SlotManager) MarkSlotDeployed

func (sm *SlotManager) MarkSlotDeployed(slot *TrackedSlot)

MarkSlotDeployed marks a specific slot as successfully deployed. Used for fallback-labeled/event slots where the UnitName may collide with a template-matched slot (e.g. a bonus troop stale-labeled "archer queen" shares a name with the real hero), so name-based lookup is unsafe.

func (*SlotManager) MarkSlotFailed

func (sm *SlotManager) MarkSlotFailed(slot *TrackedSlot)

MarkSlotFailed marks a specific slot as failed. Pointer-based variant for fallback-labeled/event slots whose UnitName may collide.

func (*SlotManager) RecordAttempt

func (sm *SlotManager) RecordAttempt(unitName string, success bool)

RecordAttempt records a deployment attempt for a slot.

type SlotState

type SlotState int

SlotState tracks lifecycle of each detected slot.

const (
	SlotDetected SlotState = iota
	SlotIdentified
	SlotAttempted
	SlotDeployed
	SlotFailed
)

func (SlotState) String

func (s SlotState) String() string

type SpellDeployer

type SpellDeployer struct {
	// contains filtered or unexported fields
}

SpellDeployer handles spell-specific deployment logic.

func NewSpellDeployer

func NewSpellDeployer(executor *TapExecutor, pCfg PrecisionConfig, formula *formula.Formula, w, h int, logger zerolog.Logger) *SpellDeployer

NewSpellDeployer creates a new spell deployer. formula may be nil; when non-nil, a formula unit entry completely replaces the legacy pCfg.SpellEdges{A,B} / pCfg.SpellTargets / isRage-special logic so the user-pinned geometry wins. slotCounts may be nil (legacy behavior: Amount=="All" resolves to the default 5 taps).

func NewSpellDeployerWithCounts

func NewSpellDeployerWithCounts(executor *TapExecutor, pCfg PrecisionConfig, formula *formula.Formula, w, h int, counts map[int]int, logger zerolog.Logger) *SpellDeployer

NewSpellDeployerWithCounts is the count-aware constructor. counts is the map returned by GetAllCounts(troopCounts): slot X -> detected count for that card.

func (*SpellDeployer) DeploySpell

func (sd *SpellDeployer) DeploySpell(unit strategy.Unit, slot *TrackedSlot, targetEdge string, phasePattern string) bool

DeploySpell deploys a spell unit according to its pattern.

Precedence:

  1. Formula entry (if loaded). Replaces ALL legacy logic including the isRage special-case so the user-pinned geometry wins.
  2. FourSides legacy fallback.
  3. Point pattern with a configured spell target.
  4. Line pattern (or default): Line A for rage, Line B otherwise.

func (*SpellDeployer) SetCounter

func (sd *SpellDeployer) SetCounter(counter *TroopCounter, barY int)

SetCounter enables post-deploy verification. counter is the shared TroopCounter already used by HeroManager/Sweeper; barY is the troop-bar top so DetectCount samples the correct card-number ROI.

func (*SpellDeployer) VerifyAndReconcile

func (sd *SpellDeployer) VerifyAndReconcile(unit strategy.Unit, slot *TrackedSlot, targetEdge, phasePattern string, maxRounds int) (int, bool)

VerifyAndReconcile re-reads the spell's slot state after deployment and re-fires the difference along the same geometry until the slot is empty or maxRounds is exhausted. Mirrors HeroManager's troop reconcile loop. A slot is only "confirmed empty" when BOTH the OCR count reads 0 and the visual empty check passes; a disagreement keeps reconciling.

Returns (extra spells fired, slot confirmed empty). Only runs when a counter was registered via SetCounter; otherwise (0, false).

type StallConfig

type StallConfig struct {
	PercentROI image.Rectangle `json:"percent_roi"`
	EndButton  image.Point     `json:"end_button"`
	ConfirmBtn image.Point     `json:"confirm_btn"`
	RefWidth   int             `json:"ref_width"`
	RefHeight  int             `json:"ref_height"`
	// ThBannerZone is an OPTIONAL reference-resolution region to scan for
	// the persistent golden "Town Hall destroyed" banner that appears above
	// the troop bar once the TH falls. When configured, the battle wait
	// latches ThDestroyed the first tick the zone contains at least
	// ThBannerGold gold pixels. Leave empty to treat the TH as unknown
	// (star counting then relies on destruction percent alone; the zone
	// was not yet calibrated from a live TH-destroyed capture).
	ThBannerZone image.Rectangle `json:"th_banner_zone,omitempty"`
	ThBannerGold int             `json:"th_banner_gold"`
}

type Sweeper

type Sweeper struct {
	// contains filtered or unexported fields
}

Sweeper handles final sweep to catch undeployed troops.

func NewSweeper

func NewSweeper(
	executor *TapExecutor,
	slotManager *SlotManager,
	pCfg PrecisionConfig,
	deployLine DeployLine,
	w, h int,
	f *formula.Formula,
	troopCounter *TroopCounter,
	autoEvent bool,
	logger zerolog.Logger,
) *Sweeper

NewSweeper creates a new sweeper. formula may be nil; when non-nil, the sweeper consults it FIRST so user-pinned _event_troop / _event_spell coordinates win over the dynamic red-zone fallback. Without this, even with a per-unit formula authored, the sweep phase re-tapped along the old red-zone line, scattering event troops to the wrong side. troopCounter may also be nil; when non-nil, the sweeper uses it to live-OCR the slot's per-card count at retry time AND runs the reconcile loop until the slot is truly empty (live count 0 AND visual-empty). This is the belt-and-braces fix for the "balloons/EDs sometimes don't all get placed" user-reported bug. autoEvent gates the event-troop dump; true (the default) places ALL bonus/seasonal troops that the strategy didn't declare.

func (*Sweeper) Sweep

func (sw *Sweeper) Sweep(strategyUnitNames []string, troopCounts map[int]int) int

Sweep deploys any remaining undeployed slots. Uses FRESH screen capture for each slot check (fixes stale screen bug).

Empty-slot guard added between batches inside deploySlot: a slot that emptied mid-batch short-circuits without firing the remaining taps. Defaults are now 1 tap (not 12) when neither troop detection nor the formula gave a count, so we don't over-deploy when the slot only held 5 troops. The previous 12 default silently wasted ~7 taps per slot.

type TapExecutor

type TapExecutor struct {
	// contains filtered or unexported fields
}

TapExecutor handles all tap operations, screen capture, and timing.

func NewTapExecutor

func NewTapExecutor(client *adb.Client, cal *game.Calibration, logger zerolog.Logger) *TapExecutor

NewTapExecutor creates a new tap executor.

func (*TapExecutor) CaptureFresh

func (t *TapExecutor) CaptureFresh() (gocv.Mat, error)

CaptureFresh captures a fresh screen from the device.

func (*TapExecutor) DeployBudgetExhausted

func (t *TapExecutor) DeployBudgetExhausted() bool

DeployBudgetExhausted reports whether the deploy phase may keep firing taps. It is the single abort check every reconcile/sweep/verify loop consults between rounds: once true, remaining units are reported undeployed and the bot moves on to the battle-end wait instead of tapping into (or past) the battle timer.

func (*TapExecutor) DeployBudgetRemaining

func (t *TapExecutor) DeployBudgetRemaining() time.Duration

DeployBudgetRemaining returns the time left in the deploy budget (0 when exhausted or never armed).

func (*TapExecutor) HumanSleep

func (t *TapExecutor) HumanSleep(baseMs, stdDevMs int)

HumanSleep wraps client HumanSleep.

func (*TapExecutor) SetSlotResolver

func (t *TapExecutor) SetSlotResolver(fn func(unitName string) *TrackedSlot)

SetSlotResolver wires the unit-name -> slot lookup used by deployers that need to correlate a unit with its slot (e.g. live OCR counts).

func (*TapExecutor) StartDeployBudget

func (t *TapExecutor) StartDeployBudget()

StartDeployBudget arms the deploy deadline at DeployBudget from now. Call once per battle before any phase runs (DeployDynamicV2).

func (*TapExecutor) StartDeployBudgetAt

func (t *TapExecutor) StartDeployBudgetAt(deadline time.Time)

StartDeployBudgetAt arms the deploy deadline at an explicit instant (used by tests to simulate an already-expired budget).

func (*TapExecutor) TapDeployFourSides

func (t *TapExecutor) TapDeployFourSides(pCfg PrecisionConfig, targetEdge string, countPerSide int, jitterPx int)

TapDeployFourSides performs rapid 4-side spam deployment.

func (*TapExecutor) TapDeployLine

func (t *TapExecutor) TapDeployLine(p1, p2 image.Point, count int, jitterPx int)

TapDeployLine distributes taps along a line from p1 to p2. Direction alternates per call (boustrophedon): down the line, then back up the next call, so consecutive passes never restart at the top.

func (*TapExecutor) TapDeployPoint

func (t *TapExecutor) TapDeployPoint(pt image.Point, count int, jitterPx int)

TapDeployPoint clusters taps around a single point.

func (*TapExecutor) TapHeroAbility

func (t *TapExecutor) TapHeroAbility(slot *TrackedSlot)

TapHeroAbility taps a hero slot for ability activation.

func (*TapExecutor) TapSlot

func (t *TapExecutor) TapSlot(slot *TrackedSlot, jitterPx int)

TapSlot selects a slot with jitter for human-like behavior.

func (*TapExecutor) WaitForSettle

func (t *TapExecutor) WaitForSettle(duration time.Duration)

WaitForSettle waits for deployment to settle.

type TrackedSlot

type TrackedSlot struct {
	TroopSlot
	State           SlotState `json:"state"`
	UnitName        string    `json:"unit_name"`
	Confidence      float64   `json:"confidence"`
	Attempts        int       `json:"attempts"`
	LastTapAt       time.Time `json:"last_tap_at"`
	IsEmpty         bool      `json:"is_empty"`
	FallbackLabeled bool      `json:"fallback_labeled"` // name came from stale manual_labels.json, not a template match
}

TrackedSlot extends TroopSlot with state tracking.

type TroopCount

type TroopCount struct {
	X          int
	Count      int
	Confidence float64
	Digits     []int
}

TroopCount represents a detected troop count for a slot.

type TroopCounter

type TroopCounter struct {
	// contains filtered or unexported fields
}

TroopCounter detects troop count numbers above each card slot. Uses template matching on digit_0..digit_9 templates to read the count.

func NewTroopCounter

func NewTroopCounter(refW, refH int, logger zerolog.Logger) *TroopCounter

NewTroopCounter creates a new troop counter with digit templates.

func (*TroopCounter) DetectCount

func (tc *TroopCounter) DetectCount(screen gocv.Mat, slot *TrackedSlot, barY int) int

DetectCount returns the live detected count above a single slot's card in the provided screen. Convenience wrapper around the per-slot OCR so HeroManager / Sweeper / Verifier can re-read counts at deploy time instead of trusting the once-cached snapshot.

Returns 0 when OCR fails or the count read is 0 — caller should treat 0 as "unknown" and combine with a visual empty check to decide whether the slot is actually empty.

func (*TroopCounter) DetectCounts

func (tc *TroopCounter) DetectCounts(screen gocv.Mat, slots []*TrackedSlot, barY int) []TroopCount

DetectCounts detects troop counts for all slots on the bar. The count number appears above each card in the troop bar.

func (*TroopCounter) HasDigitTemplates

func (tc *TroopCounter) HasDigitTemplates() bool

HasDigitTemplates returns true if digit templates are loaded.

type TroopSlot

type TroopSlot struct {
	X        int
	Y        int
	Category string
}

type UnitPlan

type UnitPlan struct {
	Unit      strategy.Unit
	Slot      *TrackedSlot
	IsSpell   bool
	IsHero    bool
	IsSiege   bool
	IsAbility bool
	Priority  int
	Retry     RetryPolicy
}

UnitPlan represents a resolved deployment plan for a single unit.

func ResolveHeroTargets

func ResolveHeroTargets(plan PhasePlan) []UnitPlan

ResolveHeroTargets returns hero units from a phase plan.

func ResolveSiegeTargets

func ResolveSiegeTargets(plan PhasePlan) []UnitPlan

ResolveSiegeTargets returns siege units from a phase plan.

func ResolveSpellTargets

func ResolveSpellTargets(plan PhasePlan) []UnitPlan

ResolveSpellTargets returns spell units from a phase plan.

func ResolveTroopTargets

func ResolveTroopTargets(plan PhasePlan) []UnitPlan

ResolveTroopTargets returns non-hero, non-spell, non-ability, non-siege units from a phase plan.

Siege machines (Stone Slammer, Battle Blimp, etc.) are intentionally EXCLUDED here. They have their own DeploySiege path with the precise touch sequence CoC expects, and including them caused the historical "siege deployed twice" bug — once as a regular troop and once as a siege machine — which double-spends the unit and confuses the verifier.

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier handles post-deployment verification.

func NewVerifier

func NewVerifier(
	executor *TapExecutor,
	slotManager *SlotManager,
	pCfg PrecisionConfig,
	targetEdge string,
	w, h int,
	config VerifyConfig,
	troopCounter *TroopCounter,
	logger zerolog.Logger,
) *Verifier

NewVerifier creates a new verifier. troopCounter may be nil; when non-nil, retryDeploy uses it to live-OCR the slot before re-firing so we never under-spot a slot whose cards still hold troops.

func (*Verifier) VerifyAll

func (v *Verifier) VerifyAll() int

VerifyAll runs comprehensive post-attack verification. Returns number of remaining undeployed slots.

type VerifyConfig

type VerifyConfig struct {
	EmptyThreshold   float64
	AbilityThreshold float64
	MaxRetryAttempts int
	RetryDelay       time.Duration
	SettleWait       time.Duration
}

VerifyConfig holds verification thresholds.

func DefaultVerifyConfig

func DefaultVerifyConfig() VerifyConfig

DefaultVerifyConfig returns default verification config.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL