Documentation
¶
Overview ¶
Package formula defines the per-unit deploy coordinate schema produced by the `design_attack` interactive tool and consumed by the deploy pipeline in `internal/attack`.
The schema is intentionally side-based (single endpoints along a chosen SIDE of the screen — top, bottom, left, right) rather than the legacy corner-based system in `precision_config.json`. The user can pinpoint exact tap coordinates for every unit type so the bot no longer has to guess based on red zone detection or random interpolation.
Schema (1 line per field, see examples in cmd/design_attack README):
{
"name": "EDrag side formula",
"screen": {"w": 860, "h": 732},
"units": {
"balloon": {"type":"line", "p1":{"x":60,"y":110}, "p2":{"x":60,"y":542}, "count":10, "jitter":3},
"stone slammer": {"type":"point", "p":{"x":400,"y":326}, "jitter":6},
"rage spell": {"type":"lines", "lines":[
{"p1":{"x":80,"y":200},"p2":{"x":130,"y":280},"count":3,"jitter":3},
{"p1":{"x":80,"y":280},"p2":{"x":130,"y":380},"count":2,"jitter":3}
]}
}
}
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Formula ¶
type Formula struct {
Name string `json:"name"`
Screen struct {
W int `json:"w"`
H int `json:"h"`
} `json:"screen"`
Units map[string]UnitEntry `json:"units"`
CornerOverrides map[string]map[string]UnitEntry `json:"corner_overrides,omitempty"`
}
Formula is the top-level deploy plan.
`Units` is the per-unit deploy geometry authored for one canonical corner (BottomRight by convention). The orchestrator mirrors Units to the active target edge at runtime (see MirrorForCorner).
`CornerOverrides` is an optional map of per-corner partial overrides. When present, the orchestrator uses the override for that corner INSTEAD OF mirroring Units — because the user has authored explicit coordinates for that side (e.g. with `cmd/design_attack -corner BL`) that better match the base's actual red-line position on that side.
Keys are the canonical corner names produced by NextEdgeIndex: "TopLeft" / "TopRight" / "BottomRight" / "BottomLeft" (case sensitive — the orchestrator's switch uses the raw value). The per-corner value is a per-unit map with the SAME schema as Units; typically a PARTIAL formula (only the units that differ from the mirrored BR default). The orchestrator merges the override with Units per-unit, with the override winning.
`corner_overrides` is omitted from JSON when nil, so old formulas (with only Units) continue to round-trip cleanly.
func AutoPickFor ¶
func AutoPickFor(s *strategy.DynamicStrategy, screenW, screenH int, targetEdge string) *Formula
AutoPickFor builds a *Formula for every unit in the strategy using per-class pinned geometry. No manual clicks required - the side is chosen from targetEdge (Random falls back to Left for determinism).
Per-class rules (4-side aware):
- Hero (Point pattern, phase name contains "hero") → midpoint of the chosen side's deploy line, jitter 5.
- Siege (Point pattern, phase name contains "siege") → dead center of map, jitter 6.
- Line troop (Line pattern, "balloon"/"dragon"/etc) → full edge line endpoints, count=10, jitter 3.
- Rage spell (Line pattern, name contains "rage") → 2 sub-lines: first 15%-60% (3 taps), second 40%-85% (2 taps).
- Other spell (Line pattern, name contains "spell") → single edge line pulled slightly inward, count=5, jitter 3.
- FourSides → point cluster at map center, jitter 5.
The 20-80 percent rule guarantees auto-pick geometry mathematically never lands in a screen corner (the regression that motivated this helper).
func Load ¶
Load finds and reads a formula file. Strategy path is the YAML file the bot is loading — we look for `<stem>_formula.json` next to it, also probing `assets/strategies/<basename>` via paths.Resolve.
func LoadFile ¶
LoadFile reads a formula from a direct path. Use Load() instead when the path is a strategy YAML (Load auto-resolves <stem>_formula.json). Used by cmd/design_attack -verify to load a previously-saved formula for visual inspection of the 4-corner mirror.
func (*Formula) ApplyScreenScale ¶
ApplyScreenScale multiplies every coordinate in the formula by (dstW/srcW, dstH/srcH) so a JSON calibrated on one screen size still lands correctly on a different live resolution. Does NOT scale Count, Jitter, or Type. No-op when src dims are zero.
Callers should pass formulaPtr.Screen.W/H as the source dims (the dimensions the formula was authored against) and the live screen dims as the destination.
func (*Formula) LookUp ¶
LookUp returns the entry for a unit name, normalizing underscores to spaces (Strategy stores "stone_slammer"; formula stores "stone slammer").
func (*Formula) MirrorForCorner ¶
MirrorForCorner reflects every P / P1 / P2 / Lines[i].P1 / Lines[i].P2 across the screen axes as needed so a formula authored for ONE corner (BottomRight by convention) applies to any of the 4 corners. Used by the orchestrator with `target_edge: "Rotate"` so the same authored attack lands on a different side each run without the user having to pin all 4 corners in `precision_config.json`.
Reflection rules (mirror around the formula's authored screen center):
BottomRight: identity (formula as-authored) — no-op BottomLeft: reflect X (newX = W - X) — flip horizontally TopRight: reflect Y (newY = H - Y) — flip vertically TopLeft: reflect both (newX = W - X; newY = H - Y)
Accepts BOTH the full canonical names ("BottomLeft", "TopRight", etc. — used by the orchestrator via NextEdgeIndex) AND the abbreviated 2-letter forms ("BL", "TR", etc. — used by cmd/design_attack -verify for its 2x2 grid labels). Freeform values like "left" or "right" also work via the substring fallback so a user-authored strategy with `target_edge: "left"` still gets a reasonable mirror.
Designed to be called BEFORE ApplyScreenScale so the mirror uses the formula's authored 860x732 reference frame. After the mirror, ApplyScreenScale does the live-screen projection. The Grand Warden "always at screen center" hardcode in HeroManager is NOT mirrored — center stays center — so a pin authored at the formula's (430, 366) center mirrors to itself, which is the right behavior either way.
type LinePoint ¶
type LinePoint struct {
P1 Point `json:"p1"`
P2 Point `json:"p2"`
Count int `json:"count"`
Jitter int `json:"jitter"`
}
LinePoint is one segment of a multi-line deploy (e.g. rage spell 3+2 split). Count = number of taps distributed along P1->P2 for this segment.
type UnitEntry ¶
type UnitEntry struct {
Type string `json:"type,omitempty"`
// point
P *Point `json:"p,omitempty"`
Jitter int `json:"jitter,omitempty"`
// line
P1 *Point `json:"p1,omitempty"`
P2 *Point `json:"p2,omitempty"`
Count int `json:"count,omitempty"`
// lines (rage-style)
Lines []LinePoint `json:"lines,omitempty"`
}
UnitEntry is the per-unit deploy instruction.
Type discriminator (inferred from populated fields):
"point" - single tap target (heroes, siege) "line" - taps evenly distributed from P1 to P2 (balloons, EDrag) "lines" - rage-style split (each LinePoint is one sub-line) empty - no entry; caller falls back to pCfg.Edges