dotbot: site area roles, a default site, site packs and the calibration span - #307
Merged
Merged
Conversation
Breaking: nothing prefers an area named `arena` any more; the simulator places robots in the site's field, the area with role `field`, else the first that is not staging or a corner. GET /controller/site now lists areas in declared order, each with its role, and names the field. AI-assisted: Claude Opus 5.5
…ith roles AI-assisted: Claude Opus 5.5
…uare AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
Breaking: area visibility moves to a new browser key holding per-area choices over role defaults, so areas a browser had hidden show again once, and corner areas start hidden. AI-assisted: Claude Opus 5.5
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #307 +/- ##
==========================================
+ Coverage 84.43% 85.11% +0.67%
==========================================
Files 209 214 +5
Lines 29264 30673 +1409
Branches 2053 2100 +47
==========================================
+ Hits 24710 26108 +1398
- Misses 4550 4561 +11
Partials 4 4
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…virtual-lab AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
… writes AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
Breaking for scripts that call the examples: motions drops --arena-size for --area (default: the controller's field), and the charging example refuses a site with no staging area. AI-assisted: Claude Opus 5.5
…first AI-assisted: Claude Opus 5.5
…s file Breaking: the controller now refuses an LH2 or camera calibration whose recorded site name differs from the active site's, or whose anchor differs when both record one, including a file passed by path. AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
…tions AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
…tests AI-assisted: Claude Opus 5.5
…site pack tests AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
The site name came from the URL or zip unchecked, so a git URL ending in `..` made the target ~/.dotbot itself, which --force then deleted. A link in a pack was followed on copy, pulling any local file into ~/.dotbot/sites. AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
…e limit AI-assisted: Claude Opus 5.5
…ement AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
points_from is now an inline table in the calibration file, e.g.
{ kind = "over", area = "dev-corner" }, and an object in the site
payload. A file carrying the old string form is refused.
AI-assisted: Claude Opus 5.5
A pack is staged in a hidden sibling folder and renamed into place, so site pack discovery skips folders whose name starts with a dot. AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
…like it Every generated robot faces the same way: positions are reported at the photodiode, a lever arm ahead of the axle, so two halves facing apart drew 300 mm between them against 200 mm everywhere else. AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
…ved ones AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
…ording AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every default that needs "the place where experiments happen" looked for an area literally named
arena:swarm calibrate-lh2 collectdefaulted toarena:corners, the console's calibration setup opened onarena, the console session API defaulted toarena:corners, and the simulator placed world-file robots inarenawhile a--robots Nfleet went tofield. A site that did not use that exact name got silent fallbacks, and a site could not say which of its areas is for parking, or that a small bench patch should stay out of the way.Around that, three gaps made a site hard to start with and hard to share. A fresh install had no site at all, so nothing worked until someone measured a room and typed a table, and the root
config_sample.tomlpredated--conn. A site lived only as an inline table in one person'sdotbot.toml, with its calibrations in a separate home directory, so handing a site to a colleague meant copying pieces by hand; nothing stopped the controller from loading a calibration made for another site. And a loaded calibration was invisible on the map: nothing showed which part of the site it actually covers, how old it is, or that robots hold homographies it never solved.Approach
This PR lands in four phases plus the follow-ups from a demo run; each is reviewable on its own (commit ranges below).
Phase 2: area roles, one field, calibration defaults
Site areas get an optional role:
field(where experiments happen and what gets calibrated),staging(where robots park and charge), orcorner(a small patch that may overlap other areas, hidden in the console until turned on). An area named after a role has it;role = "..."gives one to any other name, and an explicit role beats the name. A site has at most one field.One resolver,
Site.field, answers "which area is the field" for every reader: the area with rolefield, else the first area that is neither staging nor corner, else the first area, else the whole extent. The simulator (both placement paths),calibrate-lh2 collect, the console session API, the cameracollect --areadefault and the console's calibration setup all go through it, so "arena" no longer appears in any default.Calibration keeps the four-corner procedure but chooses the corners differently:
collectwith no flag uses the field's corners;--over <area>uses another area's corners (e.g.--over dev-cornerfor bench work);--square <mm>uses a centred square in the field and says the rest is extrapolated;--points ...is unchanged. The three are mutually exclusive. The saved calibration records how its points were chosen as apoints_fromtable,{ kind = "field" | "over" | "square" | "points", area?, side_mm? }, which the site payload carries as the same object and the console renders from its fields; it stays out of the calibration id. A file with the pre-release string form ofpoints_fromis refused rather than read. The whole field is the default because a four-point homography is tight inside its points' span and its error grows outside it; a smaller span only saves taping.GET /controller/sitelists areas in declared order with their roles and names the field. The console starts corners hidden and stores per-area visibility as{name: shown}over the role defaults. The simulator example becomes a generic site,virtual-lab, with afieldand astagingarea, and its config is a plaindotbot.toml, sodotbot run simulator --robots 200works from its folder with no-cand no--site.Phase 3: a default site from
config init, examples that read the sitedotbot config initnow writessite = "default"and a[sites.default]table: a 2 x 2 m field centred in a 5 x 5 m extent, with a 0.6 m staging strip along the field's bottom edge.--fieldsizes it (1500,1.5m,1500mm,2000x3000,1.5x2m; a bare number is mm, decimals only onm, 100 mm to 100 m, with a "did you mean 1.5m?" hint below that), everything else derives from the field, and above 5 m on a side it warns that one LH2 station rarely covers that well.--sitenames the site;--globalwrites the same template into~/.dotbot/config.toml;--forcekeeps its whole-file meaning. The site is plain TOML in the user's own file rather than a hidden built-in, so "open dotbot.toml" is the whole step from the default to a measured site.config_sample.tomlis replaced bydotbot.example.toml, equal to whatconfig initwrites, with a test that keeps the two identical. It is not nameddotbot.tomlbecause that would be discovered from the repo root and shadow~/.dotbot/config.toml.Site.stagingandSite.field_or_fallbackserve the examples:motionsplaces its shapes in an area (--area, default the field), the naming game walks inside the field, and the charging example queues on staging's border with the field, charges at staging's far edge, and parks along the field's opposite edge.Phase 4: site packs,
dotbot site add/export, refusing another site's calibrationA site pack is a folder
<name>/site.toml(the keys of[sites.<name>]) with optional<name>/calibrations/. A new top-levelsite_dirs(default["sites", "~/.dotbot/sites"], relative entries resolved from the config file's folder) lists where packs are found. An inline[sites.<name>]wins over a pack of the same name, with a one-line notice naming the pack it shadows;config showprints each site's source.Calibration lookup keeps explicit selection (path, tag or id prefix, never "latest") but searches ordered folders: the pack's
calibrations/, then~/.dotbot/calibrations/<site>/, wherecollectkeeps writing. The first folder with a match wins. At load, the controller refuses an LH2 or camera calibration whose recorded site differs from the active one, or whose anchor differs when both record one; a refused load is a CLI error, not a traceback. Push is already gated on the robots' reported site, and reframe reads another site's file by design, so neither gains the check.dotbot site add <folder|zip|git URL|->copies a pack into~/.dotbot/sites/<name>/.-reads a zip from stdin, socurl -L https://.../lab.zip | dotbot site add -works; the site's name is then the zip's top folder, assite exportwrites it, and a TTY stdin or a barehttps://...zipargument gets an error saying to pipe it. The copy is staged in a hidden sibling folder and renamed into place, so a--forcethat fails part-way leaves the old pack as it was. It refuses to overwrite without--force, and refuses a name that is not letters, digits,-and_, a pack holding links, and git's command-runningext::transport);dotbot site export <name> [--out file.zip] [--with-calibrations]writes one, turning an inline site into asite.toml. The same plain files work withgit clone,unziporcp.Phase 5: the calibrated span on the map, calibration warnings
GET /controller/sitegainscalibration: id, tag,created_at, and each placement'spoints_mmandpoints_from. The console outlines each placement's convex hull and hatches the rest of the site's extent, so an extrapolated position is visible rather than a footnote; overlapping placements leave one clear region, and a site with no extent gets the outline only. The tooltip names the tag and id, the age in days and how the points were chosen. It has its own Layers section, shown only when a calibration is loaded.The controller warns at load when the LH2 calibration is older than
[run.controller] lh2_calibration_max_age_days(default 30,0disables it, also settable through itsDOTBOT_RUN_CONTROLLER_env var), and when robots hold homographies for station indices the loaded calibration did not solve, as one line naming the stations, logged again only when that set changes. A moved station keeps its index, so age is the only guard for that case.Follow-ups from the demo run
run simulator --area <spec>places the simulated fleet (a--robots Nfleet, and any world-file robot without a position) in a named area, a+-joined composite such asfield+staging, orx,y,w,h; the default is still the field, and[run.controller] simulator_areasets it from the config. The name matches the other--areaflags (cameracollect,motions): an area spec, defaulting to the field.floor(w / pitch) * floor(h / pitch), keeping the half-pitch margin all round.calibratedin their world file, they hold exactly the stations of the controller's loaded calibration, and all eight only when none is loaded.virtual-labexample has one staging area. Its separatechargingstrip, which also hadrole = "staging", read as a second staging area in the console; the charging example uses the site's staging, which is unchanged.config shownames the active site and prints each area's role, saying when it is implied by the name.site add(it only writes a pack),config showno longer prints "No config file found" beneath a site pack it listed, and the--background-maphelp typo.Breaking changes
The project is in beta and these are clean breaks, with no compatibility shims:
collect, the session API, the console calibration setup and simulator placement use the field. A site whose experiment area is calledarenashould rename it tofieldor give itrole = "field".virtual-lab, and its config file todotbot/examples/simulator_fleet/dotbot.toml.dotbot.console.hiddenAreastodotbot.console.areaVisibility, with no migration: areas a browser had hidden show again once, and corner areas start hidden.motionsdrops--arena-sizefor--area, defaulting to the controller's field.config_sample.tomlis removed in favour ofdotbot.example.toml.--robots Nfleet is laid out differently: its grid follows the area's shape, every robot faces up, and more robots fit a non-square area. A file written with--write-init-statebefore this change still runs as written.calibratedin its world file reports the loaded calibration's stations rather than all eight.virtual-labexample drops itschargingarea.What a reviewer should check
Site.field(dotbot/site.py): role, then first non-staging/non-corner, then first area, then the extent. It decides behaviour on every existing config without roles.rolebeating the one the name implies (area_roleindotbot/area.py).--squarereporting extrapolation, and the error when two of--points/--over/--squareare given (dotbot/calibration/points.py,dotbot/cli/swarm_lh2.py).--fieldparsing and the derived layout (dotbot/cli/config_cmd.py), and thatdotbot.example.tomlstays byte-equal toconfig init's output.dotbot/site_packs.py,resolve_calibration_specindotbot/calibration/lighthouse2.py): inline beats pack, first folder wins, and anchors compared only when both sides record one (the default site and older configs record none).dotbot/controller.py). Robots do not report which stations they see, only thecalibratedbitmask of the homographies they hold, so the warning fires when robots hold a homography for an index the loaded calibration does not solve; its robot count is the count when the set of stations last changed. It cannot catch a station that is visible but never calibrated. If that reading of the requirement is wrong, this is the place to say so.dotbot/console-web/src/calibrationSpan.ts): site extent masked by the placement hulls._grid_shapeindotbot/dotbot_simulator.py): columns from the area's aspect ratio, raised so the rows fit and capped at what fits across. Rows are not always filled to the full width: a 2 x 4 m area takes 150 robots as 17 rows of 9, and 200 as 20 rows of 10.Validation
hatch test: 1285 passed, plus 5 failures specific to the local environment (they pass with a scratchHOME).b7eb271(Linux, macOS, Windows, console, documentation, readthedocs), including the Windows run, whose home-directory and path handling the site pack tests now account for.npm run build(vitest does not typecheck, so the build is the type check).pre-commit run --all-filesclean; docs build with-W --keep-going -nclean after the docs sweep, and readthedocs green.config init's default site and queued its robots along the field and staging border.dotbot run simulator --robots 50from insidedotbot/examples/simulator_fleet/, without-c:GET /controller/sitenamesvirtual-labwith its areas.config initin an empty folder, thendotbot run simulator --robots 20: sitedefault, every robot inside the field, andmotions -m squarecentred on (2500, 2500). With a C405-shaped layout, the charging example queued robots along y = 2000, charged at y = 3893 and parked at (300, 307).HOMEin a scratch folder:site export c405-arena --out c405.zip --with-calibrations,site add c405.zip, thenconfig showfrom another folder listedc405-arenafrom~/.dotbot/sites/, andrun controller --site c405-arena --lh2-calibration <id>loaded the calibration from the pack.dotbot -c <C405 config> run simulator --robots 150 --area field+staging: 150 robots in 17 rows of 9, photodiodes 200 mm apart in both directions, all inside the 2 x 4 m area;--robots 200fills it as 20 rows of 10; without--areathe same command refuses with "at most 100 do". With a world file whose robots claim all eight stations and a one-station calibration, the controller logged one warning line./controller/sitecarried the placement, and a headless screenshot of/console/showed the outline over the field with staging hatched.Nothing here reaches the robots: the firmware carries a site name and a validity rectangle, not areas, so no hardware validation applies.
Commits by phase
Review in this order; each range builds on the one before.
virtual-lab0c46279...e109de3config initdefault site,dotbot.example.toml, examples read the sitee109de3...70c1a86dotbot site70c1a86...e9c4555e9c4555...ece9501ece9501...eb41d73site addhardening, test isolation, age parsing, cleanupseb41d73...a5b6f4cpoints_from, safesite add --force,site add -a5b6f4c...520ec95run simulator --area, area-shaped grid, one staging area, warning summary, roles in the console520ec95...d790664doc/, API pages, example READMEsd790664...b7eb271Size
The PR grew from one phase to four at review time, by decision, so it is larger than a single-phase PR would be. Tests are close to half of it.
doc/and example READMEsReviewed; the docs sweep is in, and this is ready to merge.
Docs
The README quickstart now starts from
dotbot config init, then the simulator.doc/index.mdexplains sites and the three area roles;doc/cli/config.mdcovers the template,--field,--site,--globaland--force; a newdoc/cli/site.mdcoverssite add(folder, zip, git URL,-from stdin) andsite export; the configuration reference gains a Sites section (the site table, roles, the one-field rule and the fallback order, site packs andsite_dirs, where calibrations are found and the refusal of another site's,points_from) plussite,site_dirsandlh2_calibration_max_age_daysin its key tables. The LH2 calibration guide is rewritten around the field default,--over/--square/--points, the calibrate app's button, the span overlay and the refusal at load, with a note to use base-station channels 1..N with no gaps and to avoid channel 14 for now (a known issue); it drops the retired-dsquare sizing and every "arena".dotbot.site_packsjoins the API pages, thedotbot/config.pymodule example showssite_dirs, and the example READMEs rundotbot run simulator(the charging example saying it needs a staging area).Design notes
The design lives in an internal plan (rendered: https://claude.ai/artifact/Y4j8T4oW75CDU4DNre3g4s, source: https://github.com/DotBots/dotbot-workspace/blob/main/plans/site-areas/plan.html). Both links are internal; the rationale that matters for review is paraphrased above.