An aural early-game economy trainer for Age of Empires III: Definitive Edition. Watches your HUD via screen capture (no game memory reading, no overlay) and speaks short warnings when you've got idle villagers, an empty build-order queue, are about to hit the population cap with no house queued, or are low on wood for either.
Built for Skirmish/co-op-vs-AI as India at a fixed 2560x1080 fullscreen HUD. See the plan this was built from for the full design rationale.
-
Download the OCR model (not checked into git - ~9.7MB):
curl -L "https://ocrs-models.s3-accelerate.amazonaws.com/text-recognition.onnx" -o models/text-recognition.onnx -
Build:
cargo build --release
-
Run (from the project root, or copy
target/release/stickshaker.exealongsideconfig.toml,assets/, andmodels/if you move it elsewhere):./target/release/stickshaker.exe
A plain window opens (no always-on-top) with a live readout and the warning toggles. It's a normal app window - Alt+Tab to it, or park it on a second monitor, whenever you want to check or mute it.
The resource bar auto-sizes every field to its digit count ("8/10" is narrower than "10/100"), so anything after a field that can grow shifts position as the match goes on - a fixed-fraction crop for wood or idle count drifts off target within minutes. A single wide OCR pass across the whole bar was the other obvious option, but this OCR model reliably garbles that much mixed icon/text content in one line, even thresholded.
So population (cur and cap read separately - a combined "cur / cap" crop
turned out unreliable, see below) and wood are each located by
template-matching their icon within a wide search band every tick, then
read at a fixed pixel offset from wherever that icon actually is this
frame - see [anchors.*] in config.toml. Idle villagers aren't read as
a number at all: there's a fixed-position notification slot near the civ
badge that shows a sleeping-villagers icon only while idle villagers
exist, so that's a direct presence check ([roi.idle_indicator]), not
OCR - more reliable than reading a digit ever was. The build-order queue
(top-left, also fixed-position) stays a plain fractional ROI too.
Two more OCR lessons learned the hard way, both worth knowing if you end
up retuning an anchor's text offsets:
- Never let a crop span more than one field's content. A combined "cur / cap" crop reliably garbled the "/" (a live "9 / 100" came back as "0100 0", silently producing a bogus HOUSE warning); a wood crop wide enough to reach the next field's "assigned villagers" badge lost its own leading digits too ("72 (+103) [0]" -> "20103 (0"). Keep each crop to exactly the one number you need.
- Neither "as tight as possible" nor "generously wide" is reliably
correct - it has to be checked. The same "72" read as "14" when
cropped tightly and correctly at a middle size. When retuning, sweep a
few sizes through the real pipeline (see
inspect_screenshotbelow) and check the output, rather than reasoning about what "should" work.
The default anchors/ROIs in config.toml were measured against a real
2560x1080 capture and should already work, but it's worth double-checking
once before trusting it mid-match:
- Launch a skirmish, get the game running fullscreen.
- Click "Dump calibration snapshot" in the stickshaker window.
- Open
./calibration/- it has the full captured frame, the icon search band (icon_search_band.png), each found field's crop (population_cur.png,population_cap.png,wood.png), the idle indicator crop (idle_indicator.png), and every build-order queue slot (queue_slot_0.png,queue_slot_1.png, ...). - If a field says "icon not found", the icon template probably doesn't
match your HUD scale/theme - recrop
assets/templates/icon_*.pngfrom your own capture. If a found crop cuts off a digit (or includes more than one field - see below), nudge that anchor'stext = { dx0, dy0, dx1, dy1 }offsets inconfig.toml(pixels from the icon's matched top-left corner) and dump again.
You can also test against a saved screenshot file, or grab a fresh capture of whatever's currently on your screen, without going through the app:
cargo run --release --example inspect_screenshot -- path/to/screenshot.png
cargo run --release --example live_capture -- out.png- HOUSE: population is within
house_buffer(default 2) of the cap. - VILLAGER: no villager anywhere in the build-order queue.
- WOOD: any of - no villager queued and wood < 100; population near cap and wood < 80; both of those and wood < 180.
- IDLE: the idle-villager notification icon is showing.
Every threshold is in [thresholds] in config.toml. Every currently
active warning is spoken each poll tick (default every 10s), in
speak_order (default: villager, idle, wood, house) - a compound problem
(e.g. no villager queued and short on wood) is heard in full each time,
not spread across ticks.
- Requires Windows (uses SAPI for speech and Windows.Graphics.Capture via
xcapfor screen capture). - TTS phrases are synthesized once and cached as WAVs under
./cache/(gitignored) - editing a phrase inconfig.tomlinvalidates just that one cache entry. - The app polls only while
AoE3DE_s.exeis running, so it's harmless to leave open on the desktop or in menus.