Skip to content

Realistic random cursor roaming - #3

Merged
dezween merged 12 commits into
mainfrom
feat/random-roaming
Jul 12, 2026
Merged

dezween merged 12 commits into
mainfrom
feat/random-roaming

Conversation

@dezween

@dezween dezween commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Replace the invisible 1px nudge with realistic random roaming: each -interval
tick the cursor glides over ~40 small steps to a fresh uniform-random point on
the main display, like a person moving the mouse. Bounded to the screen, no
drift, interruptible mid-glide.

Type of change

  • ✨ Feature -- new user-facing behavior (visible roaming)
  • ♻️ Refactor -- removes the old nudge/Offset code
  • 🧪 Tests

What changed

  • internal/activity/domain/planner.go -- pure Planner producing a glide path
    to a random target via an injected RandomSource seam; clamps start + target
    to bounds; deterministic + GOOS=linux-buildable.
  • internal/activity/port/pointer.go -- Pointer reshaped: Position(),
    Bounds(), MoveTo(x,y); mock regenerated.
  • internal/activity/service/keeper_service.go -- KeeperService roams once per
    tick, driving the path through MoveTo with a reused timer, ctx-checked
    between steps.
  • internal/activity/adapter/cgpointer.go -- CoreGraphics Position/Bounds/
    MoveTo behind //go:build darwin, every CF object released.
  • cmd/sysmon/main.go -- runtime-seeded *math/rand/v2.Rand + planner injected;
    -interval/-quiet/signal-exit-0/accessibility preflight unchanged.
  • Removed dead Nudge/Offset domain code.

Why / context

How it was tested

  • go build ./...
  • go test -race ./... -- domain 100%, service 100%, adapter 83%
  • golangci-lint run ./... -- 0 issues
  • GOOS=linux go build ./internal/activity/{domain,port,service}/... -- cgo-free
  • Manual: cursor visibly roams the screen at random each interval; clean SIGTERM exit 0

Checklist

Architecture & code

  • Hexagonal layering respected (domain pure/GOOS=linux; service only domain+port; cgo only in adapter)
  • No new runtime dependency (stdlib math/rand/v2; testify/mock test-only)

macOS / cgo

  • Every CoreFoundation object released; //go:build darwin; MoveTo posts a real event

Concurrency & resources

  • Interruptible mid-glide (ctx between steps); reused timer; ticker sleep between roams (low idle CPU)

Safety & correctness

  • Start + target clamped to bounds -- never off-screen, no drift (multi-monitor safe)
  • roam abandons cleanly on Position/Bounds/Plan/MoveTo errors without crashing the loop

Reviewer notes

Four review passes (2x GSD code-review, 2x project review prompt) applied; all
findings fixed down to nit level. CI runs build/lint/test on macOS only.

dezween added 12 commits July 12, 2026 03:11
…deterministic tests

- Planner.Plan interpolates from current position to a fresh uniform random target, clamped to screen bounds
- randomness injected via a minimal RandomSource seam so tests are deterministic
- path is continuous, ends exactly at target, covers corner/1x1/target-equals-current edge cases
- cgo/os-free, builds under GOOS=linux
…ing and regenerate mock

- replace Nudge(dx, dy int) error with MoveTo(x, y int) error, Position(), and Bounds()
- regenerate mockgen mock from the new interface (MoveTo/Position/Bounds)

Note: this leaves the tree non-building until the service/adapter/main cutover in the following tasks, as planned.
… tick

- roam(ctx): read Position/Bounds, ask domain.Planner for a glide path, then drive() calls MoveTo per path point with a short inter-step pause
- ctx checked between every step so cancellation stops promptly mid-glide
- Position/Bounds/MoveTo errors are logged and abandon the current roam without crashing the loop
- tests rewritten with synctest + regenerated mockgen mock: one roam per tick, ordered MoveTo, mid-glide cancel, per-call error handling, start/stop logging

Note: this leaves the tree non-building until the adapter/main cutover in the following tasks, as planned.
…o to CGPointer

- getPosition/getBounds/moveTo C helpers replace the relative nudge helper: current cursor location via CGEventGetLocation, main-display bounds via CGDisplayBounds(CGMainDisplayID()), absolute move via a MouseMoved event
- every CoreFoundation object created is released; NULL-create failures release nothing and signal failure
- ErrNudgeFailed split into ErrPositionFailed and ErrMoveFailed
- compile-time assertion that *CGPointer satisfies port.Pointer
- tests updated to the new API (position/bounds/move-to-current-position smoke tests, no CI mouse movement)
…move dead Offset domain code

- construct a runtime-seeded *math/rand/v2.Rand (rand.NewPCG from the auto-seeded package generator) and a domain.Planner, wired into KeeperService alongside the pointer/logger/interval
- remove nudgeOffset constant and the domain.NewOffset validation
- update flag help text and package doc comment to describe random roaming instead of the 1px nudge
- keep the AccessibilityTrusted preflight warning and SIGINT/SIGTERM clean-exit-0 signal handling unchanged
- delete domain.Offset, NewOffset, and ErrNonPositiveOffset (and their tests): roaming uses absolute clamped targets, so a relative offset value object no longer has meaning
- fix a stale ErrNudgeFailed reference in a service test comment
Addresses roaming code review #1:
- CR-01: clamp the starting cursor position into the display bounds before
  interpolating, so a cursor on a secondary monitor (global coords) cannot send
  early glide steps off-screen; covered by a new planner test
- WR-01: reuse a single time.Timer across the glide instead of time.After per step
- WR-02/IN-01/IN-02: clarify rounding, bounds-per-roam, and adapter Bounds contract
Addresses roaming code review #2 (IN-01, IN-02): restore the OnePixelScreen doc
comment to its function and make the clamp test table-driven, adding the exact
boundary case (curX == width).
… service seam

Addresses custom-prompt review #1 (P2):
- start the reused glide timer stopped so the first inter-step gap is exactly
  stepPause
- add a service synctest asserting every MoveTo stays within [0,w)x[0,h) even
  when Position() reports an out-of-bounds start
- service: invalid-bounds (Plan error) abandon path and move-error-on-first-step
  abandon -- service coverage now 100%
- planner: very large screen, bounded step delta corner-to-corner, and
  different-seed-different-path cases
@dezween
dezween merged commit 37b8c3f into main Jul 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant